Thumbnails for images and videos #87

Merged
Anna Sirota merged 28 commits from thumbnails into main 2024-04-25 17:50:58 +02:00
10 changed files with 86 additions and 36 deletions
Showing only changes of commit e03ec2ae95 - Show all commits

View File

@ -1,5 +1,4 @@
from typing import Set, Tuple, Mapping, Any from typing import Set, Tuple, Mapping, Any
import copy
import logging import logging
from django.contrib.admin.models import DELETION from django.contrib.admin.models import DELETION
@ -21,6 +20,13 @@ See TrackChangesMixin.pre_save_record().
""" """
def _get_object_state(obj: object, fields=None, include_pk=False) -> dict:
data = serializers.serialize('python', [obj], fields=fields)[0]
if include_pk:
data['fields']['pk'] = data['pk']
return data['fields']
class CreatedModifiedMixin(models.Model): class CreatedModifiedMixin(models.Model):
"""Add standard date fields to a model.""" """Add standard date fields to a model."""
@ -48,11 +54,6 @@ class CreatedModifiedMixin(models.Model):
class RecordDeletionMixin: class RecordDeletionMixin:
def serialise(self) -> dict:
data = serializers.serialize('python', [self])[0]
data['fields']['pk'] = data['pk']
return data['fields']
def record_deletion(self): def record_deletion(self):
"""Create a LogEntry describing a deletion of this object.""" """Create a LogEntry describing a deletion of this object."""
msg_args = {'type': type(self), 'pk': self.pk} msg_args = {'type': type(self), 'pk': self.pk}
@ -63,7 +64,7 @@ class RecordDeletionMixin:
# This shouldn't happen: prior validation steps should have taken care of this. # This shouldn't happen: prior validation steps should have taken care of this.
msg_args['reasons'] = cannot_be_deleted_reasons msg_args['reasons'] = cannot_be_deleted_reasons
logger.error("%(type)s pk=%(pk)s is being deleted but it %(reasons)s", msg_args) logger.error("%(type)s pk=%(pk)s is being deleted but it %(reasons)s", msg_args)
state = self.serialise() state = _get_object_state(self, include_pk=True)
message = [ message = [
{ {
'deleted': { 'deleted': {
@ -123,9 +124,7 @@ class TrackChangesMixin(RecordDeletionMixin, models.Model):
update_fields = kwargs.get('update_fields') update_fields = kwargs.get('update_fields')
was_modified = self._was_modified(db_instance, update_fields=update_fields) was_modified = self._was_modified(db_instance, update_fields=update_fields)
old_instance_data = { old_instance_data = _get_object_state(db_instance, fields=self.track_changes_to_fields)
attr: copy.deepcopy(getattr(db_instance, attr)) for attr in self.track_changes_to_fields
}
return was_modified, old_instance_data return was_modified, old_instance_data
def record_status_change(self, was_changed, old_state, **kwargs): def record_status_change(self, was_changed, old_state, **kwargs):
@ -151,8 +150,9 @@ class TrackChangesMixin(RecordDeletionMixin, models.Model):
if not was_changed or not self.pk: if not was_changed or not self.pk:
return return
new_state = _get_object_state(self, fields=self.track_changes_to_fields)
changed_fields = { changed_fields = {
field for field in old_state.keys() if getattr(self, field) != old_state[field] field for field in old_state.keys() if new_state.get(field) != old_state.get(field)
} }
message = [ message = [
{ {

View File

@ -1,3 +1,4 @@
from django.core.exceptions import ValidationError
from semantic_version.django_fields import VersionField as SemanticVersionField from semantic_version.django_fields import VersionField as SemanticVersionField
from semantic_version import Version from semantic_version import Version
import json import json
@ -11,7 +12,10 @@ class VersionStringField(SemanticVersionField):
return value return value
if value is None: if value is None:
return value return value
try:
return str(Version(value)) return str(Version(value))
except Exception as e:
raise ValidationError(e)
def from_db_value(self, value, expression, connection): def from_db_value(self, value, expression, connection):
return self.to_python(value) return self.to_python(value)

View File

@ -3,6 +3,18 @@
<a <a
href="https://www.blender.org/download/releases/{{ version.blender_version_min|version_without_patch|replace:".,-" }}/" href="https://www.blender.org/download/releases/{{ version.blender_version_min|version_without_patch|replace:".,-" }}/"
title="{{ version.blender_version_min }}">Blender {{ version.blender_version_min|version_without_patch }}</a> title="{{ version.blender_version_min }}">Blender {{ version.blender_version_min|version_without_patch }}</a>
{% if is_editable %}
&mdash;
<input name="blender_version_max" class="form-control-sm"
value="{{version.blender_version_max|default_if_none:''}}"
placeholder="{% trans 'maximum Blender version' %}"
pattern="^([0-9]+\.[0-9]+\.[0-9]+)?$"
title="{% trans 'Blender version, e.g. 4.1.0' %}"
/>
{% for error in form.errors.blender_version_max %}
<div class="error">{{ error }}</div>
{% endfor %}
{% else %}
{% if version.blender_version_max %} {% if version.blender_version_max %}
{% if version.blender_version_max|version_without_patch != version.blender_version_min|version_without_patch %} {% if version.blender_version_max|version_without_patch != version.blender_version_min|version_without_patch %}
&mdash; &mdash;
@ -13,3 +25,4 @@
{% else %} {% else %}
{% trans 'and newer' %} {% trans 'and newer' %}
{% endif %} {% endif %}
{% endif %}

View File

@ -54,7 +54,7 @@
<div class="dl-row"> <div class="dl-row">
<div class="dl-col"> <div class="dl-col">
<dt>{% trans 'Tagline' %}</dt> <dt>{% trans 'Tagline' %}</dt>
<dd title="{{ latest.tagline }}">{{ latest.tagline }}</dd> <dd title="{{ version.tagline }}">{{ version.tagline }}</dd>
</div> </div>
</div> </div>
@ -63,20 +63,20 @@
<dt>{% trans 'Version' %}</dt> <dt>{% trans 'Version' %}</dt>
<dd> <dd>
<a href="{{ extension.get_versions_url }}"> <a href="{{ extension.get_versions_url }}">
{{ latest.version }} {{ version.version }}
</a> </a>
</dd> </dd>
</div> </div>
<div class="dl-col"> <div class="dl-col">
<dt>{% trans 'Size' %}</dt> <dt>{% trans 'Size' %}</dt>
<dd>{{ latest.file.size_bytes|filesizeformat }}</dd> <dd>{{ version.file.size_bytes|filesizeformat }}</dd>
</div> </div>
</div> </div>
<div class="dl-row"> <div class="dl-row">
<div class="dl-col"> <div class="dl-col">
<dt>{% trans 'Compatibility' %}</dt> <dt>{% trans 'Compatibility' %}</dt>
<dd>{% include "extensions/components/blender_version.html" with version=latest %}</dd> <dd>{% include "extensions/components/blender_version.html" with version=version is_editable=is_editable form=form %}</dd>
</div> </div>
</div> </div>
@ -91,8 +91,8 @@
<div class="dl-row"> <div class="dl-row">
<div class="dl-col"> <div class="dl-col">
<dt>License{{ latest.licenses.count|pluralize }}</dt> <dt>License{{ version.licenses.count|pluralize }}</dt>
{% for license in latest.licenses.all %} {% for license in version.licenses.all %}
<dd> <dd>
{% include "common/components/external_link.html" with url=license.url title=license %} {% include "common/components/external_link.html" with url=license.url title=license %}
</dd> </dd>
@ -102,14 +102,14 @@
<div class="dl-row"> <div class="dl-row">
<div class="dl-col"> <div class="dl-col">
{% include "extensions/components/detail_card_version_permissions.html" with version=latest %} {% include "extensions/components/detail_card_version_permissions.html" with version=version %}
</div> </div>
</div> </div>
<div class="dl-row"> <div class="dl-row">
<dd> <dd>
{% if latest.tags.count %} {% if version.tags.count %}
{% include "extensions/components/tags.html" with small=True version=latest %} {% include "extensions/components/tags.html" with small=True version=version %}
{% else %} {% else %}
No tags. No tags.
{% endif %} {% endif %}

View File

@ -78,7 +78,7 @@
<div class="is-sticky py-3"> <div class="is-sticky py-3">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{% include "extensions/components/extension_edit_detail_card.html" with extension=form.instance.extension latest=form.instance is_initial=True %} {% include "extensions/components/extension_edit_detail_card.html" with extension=form.instance.extension version=form.instance is_initial=True %}
<section class="card p-3 mt-3"> <section class="card p-3 mt-3">
<div class="btn-col"> <div class="btn-col">

View File

@ -4,7 +4,7 @@
{% block page_title %}{{ extension.name }}{% endblock page_title %} {% block page_title %}{{ extension.name }}{% endblock page_title %}
{% block content %} {% block content %}
{% with latest=extension.latest_version author=extension.latest_version.file.user form=form|add_form_classes %} {% with author=extension.latest_version.file.user form=form|add_form_classes %}
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<h2>{{ extension.get_type_display }} {% trans 'details' %}</h2> <h2>{{ extension.get_type_display }} {% trans 'details' %}</h2>
@ -93,7 +93,7 @@
<div class="is-sticky py-3"> <div class="is-sticky py-3">
<div class="row mb-3"> <div class="row mb-3">
<div class="col"> <div class="col">
{% include "extensions/components/extension_edit_detail_card.html" with extension=extension latest=latest %} {% include "extensions/components/extension_edit_detail_card.html" with extension=extension version=extension.latest_version %}
<section class="card p-3 mt-3"> <section class="card p-3 mt-3">
<div class="btn-col"> <div class="btn-col">

View File

@ -44,8 +44,7 @@
<div class="is-sticky"> <div class="is-sticky">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{% include "extensions/components/extension_edit_detail_card.html" with extension=form.instance.extension latest=form.instance %} {% include "extensions/components/extension_edit_detail_card.html" with extension=form.instance.extension version=form.instance is_editable=True form=form %}
<section class="card p-3 mt-3"> <section class="card p-3 mt-3">
<div class="btn-col"> <div class="btn-col">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">

View File

@ -289,3 +289,36 @@ class UpdateVersionViewTest(_BaseTestCase):
self.client.force_login(random_user) self.client.force_login(random_user)
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
def test_blender_max_version(self):
extension = _create_extension()
extension_owner = extension.latest_version.file.user
extension.authors.add(extension_owner)
self.client.force_login(extension_owner)
url = reverse(
'extensions:version-update',
kwargs={
'type_slug': extension.type_slug,
'slug': extension.slug,
'pk': extension.latest_version.pk,
},
)
version = extension.latest_version
response = self.client.post(
url,
{'release_notes': 'text', 'blender_version_max': 'invalid'},
)
# error page, no redirect
self.assertEqual(response.status_code, 200)
version.refresh_from_db()
self.assertIsNone(version.blender_version_max)
response2 = self.client.post(
url,
{'release_notes': 'text', 'blender_version_max': '4.2.0'},
)
# success, redirect
self.assertEqual(response2.status_code, 302)
version.refresh_from_db()
self.assertEqual(version.blender_version_max, '4.2.0')

View File

@ -327,7 +327,7 @@ class UpdateVersionView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
template_name = 'extensions/new_version_finalise.html' template_name = 'extensions/new_version_finalise.html'
model = Version model = Version
fields = ['release_notes'] fields = ['blender_version_max', 'release_notes']
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(

View File

@ -7,6 +7,7 @@ from django.contrib.admin.utils import NestedObjects
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models, DEFAULT_DB_ALIAS, transaction from django.db import models, DEFAULT_DB_ALIAS, transaction
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.dateparse import parse_datetime
from common.model_mixins import TrackChangesMixin from common.model_mixins import TrackChangesMixin
from files.utils import get_sha256_from_value from files.utils import get_sha256_from_value
@ -89,7 +90,7 @@ class User(TrackChangesMixin, AbstractUser):
date_deletion_requested, date_deletion_requested,
) )
self.is_active = False self.is_active = False
self.date_deletion_requested = date_deletion_requested self.date_deletion_requested = parse_datetime(date_deletion_requested)
self.save(update_fields=['is_active', 'date_deletion_requested']) self.save(update_fields=['is_active', 'date_deletion_requested'])
@transaction.atomic @transaction.atomic