Extensions list: sort_by parameter #159

Merged
Márton Lente merged 36 commits from filter-sort into main 2024-06-03 12:57:45 +02:00
33 changed files with 178 additions and 131 deletions
Showing only changes of commit 9182ed7543 - Show all commits

View File

@ -0,0 +1,12 @@
name: Bug Report
about: Report issues for the Extensions Platform website.
labels:
- "Type/Report"
- "Status/Needs Triage"
- "Priority/Normal"
body:
- type: textarea
id: body
attributes:
label: "Description"
hide_label: true

View File

@ -0,0 +1,34 @@
name: Team Management
about: Form to request creation of a team, or joining existing ones.
labels:
- "Type/Team"
body:
- type: markdown
attributes:
value: |
### Instructions
This form is to create, delete, or join a team.
You can leave teams by yourself from the [Teams page](https://extensions.blender.org/settings/teams/) on your profile.
- type: textarea
id: body
attributes:
label: "Description"
hide_label: true
value: |
**Team Management Request**
Mark the type of request you have:
- [ ] Create a team
- [ ] Join a team
- [ ] Add a member to my team
- [ ] Remove a member from my team
- [ ] Delete a team
**Team Information**
Name:
URL: e.g. https://extensions.blender.org/team/community/
**User Details**
Name of the user to add/remove:
Profile URL:

1
.gitignore vendored
View File

@ -68,6 +68,7 @@ wheels/
*.egg *.egg
*.manifest *.manifest
*.spec *.spec
*.DS_Store
# Installer logs # Installer logs
pip-log.txt pip-log.txt

View File

@ -1,4 +1,5 @@
{% extends 'users/settings/base.html' %} {% extends 'users/settings/base.html' %}
{% block page_title %}Tokens{% endblock %}
{% block settings %} {% block settings %}
<h1 class="mb-3">Tokens</h1> <h1 class="mb-3">Tokens</h1>

View File

@ -31,9 +31,6 @@ a.badge-tag
border-color: var(--color-text) border-color: var(--color-text)
color: var(--color-text) color: var(--color-text)
.badge-tag
font-size: var(--fs-xs)
.badge-status .badge-status
&-approved, &-approved,
&-resolved &-resolved

View File

@ -369,9 +369,11 @@
+padding(3, x) +padding(3, x)
+padding(0, y) +padding(0, y)
transition: background-color var(--transition-speed-fast) transition: background-color var(--transition-speed-fast)
vertical-align: baseline
a a:not(.btn)
color: var(--color-text) color: var(--color-text)
display: inline-block
+padding(1, y) +padding(1, y)
padding-inline: 0 !important padding-inline: 0 !important
@ -390,6 +392,10 @@
.ext-review-list-activity .ext-review-list-activity
width: 1% width: 1%
.ext-review-list-status a
vertical-align: text-top
width: 100%
.rating-form .rating-form
select select
color: var(--color-warning) color: var(--color-warning)

View File

@ -102,7 +102,7 @@
/* Lightbox component. */ /* Lightbox component. */
.galleria .galleria
--galleria-btn-width: 100px --galleria-btn-width: 12.8rem
--galleria-media-max-width: 100% --galleria-media-max-width: 100%
+media-lg +media-lg
@ -148,14 +148,18 @@
&.btn-close &.btn-close
font-size: 3.2rem font-size: 3.2rem
height: 20vh height: 20vh
max-height: 80px max-height: 8.0rem
max-width: 80px max-width: var(--galleria-btn-width)
position: absolute position: absolute
right: 0 right: 0
top: 0 top: 0
width: 80px width: var(--galleria-btn-width)
z-index: 2 z-index: 2
&.btn-close,
&.btn-next,
text-align: right
&.btn-next, &.btn-next,
&.btn-prev &.btn-prev
top: 50% top: 50%
@ -166,6 +170,12 @@
&.btn-prev &.btn-prev
left: 0 left: 0
text-align: left
[class^="i-"]::before,
[class*=" i-"]::before
margin-left: 0
margin-right: 0
.underlay .underlay
background-color: rgba(0,0,0,0.9) background-color: rgba(0,0,0,0.9)

View File

@ -186,7 +186,7 @@
<li class="dropdown-divider"></li> <li class="dropdown-divider"></li>
<li> <li>
<a href="https://projects.blender.org/infrastructure/extensions-website/issues" class="dropdown-item"> <a href="https://projects.blender.org/infrastructure/extensions-website/issues/new?template=.gitea/issue_template/bug.yaml" class="dropdown-item" target="_blank">
<i class="i-alert-triangle"></i> {% trans 'Report Problem' %} <i class="i-alert-triangle"></i> {% trans 'Report Problem' %}
</a> </a>
</li> </li>

View File

@ -656,10 +656,9 @@ class Version(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Model
# TODO: actual signed URLs? # TODO: actual signed URLs?
return self.file.source.url return self.file.source.url
@property def download_url(self, append_repository=True) -> str:
def download_url(self) -> str:
filename = f'{self.extension.type_slug_singular}-{self.extension.slug}-v{self.version}.zip' filename = f'{self.extension.type_slug_singular}-{self.extension.slug}-v{self.version}.zip'
return reverse( download_url = reverse(
'extensions:version-download', 'extensions:version-download',
kwargs={ kwargs={
'type_slug': self.extension.type_slug, 'type_slug': self.extension.type_slug,
@ -668,6 +667,9 @@ class Version(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Model
'filename': filename, 'filename': filename,
}, },
) )
if append_repository:
download_url += '?repository=/api/v1/extensions/'
return download_url
def get_delete_url(self) -> str: def get_delete_url(self) -> str:
return reverse( return reverse(

View File

@ -69,9 +69,11 @@
{% trans "Reviews" %} {% trans "Reviews" %}
</a> </a>
{% endif %} {% endif %}
{% if extension.latest_version != None %}
<a href="{{ extension.get_versions_url }}" class="{% if '/versions/' in request.get_full_path %}is-active{% endif %}"> <a href="{{ extension.get_versions_url }}" class="{% if '/versions/' in request.get_full_path %}is-active{% endif %}">
{% trans "Version History" %} {% trans "Version History" %}
</a> </a>
{% endif %}
</div> </div>
<div class="btn-row hero-tabs-admin mb-3 mb-md-0"> <div class="btn-row hero-tabs-admin mb-3 mb-md-0">
{% if is_maintainer %} {% if is_maintainer %}

View File

@ -1,6 +1,6 @@
{% load extensions %} {% load extensions %}
<a href="{% url "extensions:by-tag" tag_slug=tag.slug %}" title="{{ tag.name }}" <a href="{% url "extensions:by-tag" tag_slug=tag.slug %}" title="{{ tag.name }}"
class="badge badge-tag{% if not small %} badge-lg{% endif %} {{ class }}"> class="badge badge-tag {{ classes }}">
{{ tag.name }} {{ tag.name }}
</a> </a>

View File

@ -66,7 +66,7 @@
<ul class="flex-wrap"> <ul class="flex-wrap">
{% for tag in latest.tags.all %} {% for tag in latest.tags.all %}
<li class="mb-1"> <li class="mb-1">
{% include "extensions/components/badge_tag.html" with small=True version=latest %} {% include "extensions/components/badge_tag.html" with version=latest classes="badge-sm" %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -1,9 +1,9 @@
<div class="ext-detail-permissions"> <div class="ext-detail-permissions">
<dt>Permissions</dt> <dt>Permissions</dt>
{% if version.permissions.all %} {% if permissions %}
<dd>This version requests the following: <dd>This version requests the following:
<ul> <ul>
{% for p in version.permissions.all %} {% for p in permissions %}
<li> <li>
<div> <div>
<i class="i-permission-{{ p.slug }}"></i> <i class="i-permission-{{ p.slug }}"></i>

View File

@ -104,14 +104,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=version %} {% include "extensions/components/detail_card_version_permissions.html" with permissions=version.permissions.all %}
</div> </div>
</div> </div>
<div class="dl-row"> <div class="dl-row">
<dd class="ext-detail-info-tags"> <dd class="ext-detail-info-tags">
{% if version.tags.count %} {% if version.tags.count %}
{% include "extensions/components/tags.html" with small=True version=version %} {% include "extensions/components/tags.html" with version=version %}
{% else %} {% else %}
No tags. No tags.
{% endif %} {% endif %}

View File

@ -66,11 +66,12 @@
<hr class="my-4"> <hr class="my-4">
<section id="permissions" class="ext-detail-permissions"> <section id="permissions" class="ext-detail-permissions">
<h2 class="mb-3">{% trans "Permissions" %}</h2> <h2 class="mb-3">{% trans "Permissions" %}</h2>
{% if latest.permissions.all %} {% with permissions=latest.permissions.all %}
{% if permissions %}
<div class="box"> <div class="box">
<p>This extension requests the following permission{{ latest.permissions.all|pluralize }}:</p> <p>This extension requests the following permission{{ permissions|pluralize }}:</p>
<ul> <ul>
{% for p in latest.permissions.all %} {% for p in permissions %}
<li> <li>
<div> <div>
<i class="i-permission-{{ p.slug }}"></i> <i class="i-permission-{{ p.slug }}"></i>
@ -86,6 +87,7 @@
{% else %} {% else %}
<p class="mb-0"><i class="i-check me-0"></i> This extension does not require special permissions.</p> <p class="mb-0"><i class="i-check me-0"></i> This extension does not require special permissions.</p>
{% endif %} {% endif %}
{% endwith %}
</section> </section>
{% endif %} {% endif %}
{% endblock extension_permissions %} {% endblock extension_permissions %}
@ -249,7 +251,9 @@
</button> </button>
</div> </div>
<small class="d-block text-center w-100"> <small class="d-block text-center w-100">
or manually <a class="text-muted text-underline" href="{{ request.scheme }}://{{ request.get_host }}{{ latest.download_url }}" download="{{ latest.download_name }}">download</a> and install it. {# TODO @front-end: Replace URL of the manual /dev/ with /latest/. #}
...or <a class="text-underline text-primary" href="{{ request.scheme }}://{{ request.get_host }}{{ latest.download_url }}" download="{{ latest.download_name }}">download</a>
and <a class="text-underline text-primary" href="https://docs.blender.org/manual/en/dev/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a>
</small> </small>
</div> </div>
{% else %} {% else %}

View File

@ -1,5 +1,5 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load cache i18n %} {% load i18n %}
{% block page_title %}Extensions{% endblock page_title %} {% block page_title %}Extensions{% endblock page_title %}
@ -32,7 +32,6 @@
{% endblock hero %} {% endblock hero %}
{% block content %} {% block content %}
{% cache 60 home %}
<section class="mt-3"> <section class="mt-3">
<div class="d-flex"> <div class="d-flex">
<h2> <h2>
@ -78,5 +77,4 @@
</a> </a>
</div> </div>
</section> </section>
{% endcache %}
{% endblock content %} {% endblock content %}

View File

@ -20,6 +20,18 @@
const input = document.getElementById('id_{{ image_form.prefix }}-source'); const input = document.getElementById('id_{{ image_form.prefix }}-source');
const previewEl = document.getElementsByClassName('{{ image_form.prefix }}-preview')[0]; const previewEl = document.getElementsByClassName('{{ image_form.prefix }}-preview')[0];
const previewElIcon = previewEl.querySelector('.js-i-image'); const previewElIcon = previewEl.querySelector('.js-i-image');
const previewElComputedStyle = window.getComputedStyle(previewEl);
const previewElBgImg = previewElComputedStyle.getPropertyValue('background-image');
function hidePreviewElIcon() {
previewElIcon.classList.add('d-none');
}
// Check if previewElBgImg is set
if (previewElBgImg.length > 10) {
// Hide previewElIcon
hidePreviewElIcon();
}
input.addEventListener('change', function() { input.addEventListener('change', function() {
const curFiles = input.files; const curFiles = input.files;
@ -27,7 +39,8 @@
const dataUrl = URL.createObjectURL(curFiles[0]); const dataUrl = URL.createObjectURL(curFiles[0]);
previewEl.style['background-image'] = `url("${dataUrl}")`; previewEl.style['background-image'] = `url("${dataUrl}")`;
previewElIcon.classList.add('d-none');
hidePreviewElIcon();
} }
}); });

View File

@ -74,7 +74,7 @@
</div> </div>
<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=version %} {% include "extensions/components/detail_card_version_permissions.html" with permissions=version.permissions.all %}
</div> </div>
</div> </div>
<div class="dl-row"> <div class="dl-row">

View File

@ -66,7 +66,7 @@ class VersionUploadAPITest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual( self.assertEqual(
response.data['message'], response.data['message'],
f'Extension "{other_extension.extension_id}" not maintained by user "{self.user.full_name}"', f'Extension "{other_extension.extension_id}" not maintained by user "{self.user.username}"',
) )
def test_version_upload_extension_does_not_exist(self): def test_version_upload_extension_does_not_exist(self):

View File

@ -122,12 +122,20 @@ class ApiViewsTest(_BaseTestCase):
create_approved_version() create_approved_version()
url = reverse('extensions:api') url = reverse('extensions:api')
# returns 1st and 3rd items
json = self.client.get( json = self.client.get(
url + '?platform=windows-amd64', url + '?platform=windows-amd64',
HTTP_ACCEPT='application/json', HTTP_ACCEPT='application/json',
).json() ).json()
self.assertEqual(len(json['data']), 2) self.assertEqual(len(json['data']), 2)
# only returns the 3rd item
json = self.client.get(
url + '?platform=platform-we-dont-know',
HTTP_ACCEPT='application/json',
).json()
self.assertEqual(len(json['data']), 1)
def test_blender_version_filter_latest_not_max_version(self): def test_blender_version_filter_latest_not_max_version(self):
version = create_approved_version(blender_version_min='4.0.1') version = create_approved_version(blender_version_min='4.0.1')
version.date_created version.date_created

View File

@ -27,14 +27,14 @@ class ListedExtensionsSerializer(serializers.ModelSerializer):
error_messages = { error_messages = {
"invalid_blender_version": "Invalid blender_version: use full semantic versioning like " "invalid_blender_version": "Invalid blender_version: use full semantic versioning like "
"4.2.0.", "4.2.0.",
"invalid_platform": "Invalid platform: use notation specified in "
"https://developer.blender.org/docs/features/extensions/schema/1.0.0/",
} }
class Meta: class Meta:
model = Extension model = Extension
fields = () fields = ()
UNKNOWN_PLATFORM = 'unknown-platform-value'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None) self.request = kwargs.pop('request', None)
self.blender_version = kwargs.pop('blender_version', None) self.blender_version = kwargs.pop('blender_version', None)
@ -53,7 +53,7 @@ class ListedExtensionsSerializer(serializers.ModelSerializer):
try: try:
Platform.objects.get(slug=self.platform) Platform.objects.get(slug=self.platform)
except Platform.DoesNotExist: except Platform.DoesNotExist:
self.fail('invalid_platform') self.platform = self.UNKNOWN_PLATFORM
def to_representation(self, instance): def to_representation(self, instance):
matching_version = None matching_version = None
@ -75,7 +75,8 @@ class ListedExtensionsSerializer(serializers.ModelSerializer):
continue continue
platform_slugs = set(p.slug for p in v.platforms.all()) platform_slugs = set(p.slug for p in v.platforms.all())
# empty platforms field matches any platform filter # empty platforms field matches any platform filter
if self.platform and not (not platform_slugs or self.platform in platform_slugs): # UNKNOWN_PLATFORM matches only empty platforms field
if self.platform and (platform_slugs and self.platform not in platform_slugs):
continue continue
matching_version = v matching_version = v
break break
@ -91,7 +92,9 @@ class ListedExtensionsSerializer(serializers.ModelSerializer):
'tagline': matching_version.tagline, 'tagline': matching_version.tagline,
'archive_hash': matching_version.file.original_hash, 'archive_hash': matching_version.file.original_hash,
'archive_size': matching_version.file.size_bytes, 'archive_size': matching_version.file.size_bytes,
'archive_url': self.request.build_absolute_uri(matching_version.download_url), 'archive_url': self.request.build_absolute_uri(
matching_version.download_url(append_repository=False)
),
'type': EXTENSION_TYPE_SLUGS_SINGULAR.get(instance.type), 'type': EXTENSION_TYPE_SLUGS_SINGULAR.get(instance.type),
'blender_version_min': matching_version.blender_version_min, 'blender_version_min': matching_version.blender_version_min,
'blender_version_max': matching_version.blender_version_max, 'blender_version_max': matching_version.blender_version_max,

View File

@ -5,8 +5,6 @@ from django.contrib import admin
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
import background_task.admin
import background_task.models
from .models import File, FileValidation from .models import File, FileValidation
import files.signals import files.signals
@ -175,48 +173,3 @@ class FileAdmin(admin.ModelAdmin):
return obj.validation.is_ok if hasattr(obj, 'validation') else None return obj.validation.is_ok if hasattr(obj, 'validation') else None
is_ok.boolean = True is_ok.boolean = True
try:
admin.site.unregister(background_task.models.Task)
admin.site.unregister(background_task.models.CompletedTask)
except admin.site.NotRegistered:
pass
class TaskMixin:
"""Modify a few properties of background tasks displayed in admin."""
def no_errors(self, obj):
"""Replace background_task's "has_error".
Make Django's red/green boolean icons less confusing
in the context of "there's an error during task run".
"""
return not bool(obj.last_error)
no_errors.boolean = True
@admin.register(background_task.models.Task)
@admin.register(background_task.models.CompletedTask)
class TaskAdmin(background_task.admin.TaskAdmin, TaskMixin):
date_hierarchy = 'run_at'
list_display = [
'run_at',
'task_name',
'task_params',
'attempts',
'no_errors',
'locked_by',
'locked_by_pid_running',
]
list_filter = (
'task_name',
'run_at',
'failed_at',
'locked_at',
'attempts',
'creator_content_type',
)
search_fields = ['task_name', 'task_params', 'last_error', 'verbose_name']

View File

@ -228,7 +228,7 @@ class TestTasks(TestCase):
expected_abuse_report_text = """Add-on reported: "{extension.name}" expected_abuse_report_text = """Add-on reported: "{extension.name}"
{some_user.full_name} reported Add-on "{extension.name}" {some_user.username} reported Add-on "{extension.name}"
: :
test message test message
@ -243,7 +243,7 @@ https://extensions.local:8111/
""" """
expected_new_comment_text = """New comment on Add-on "{extension.name}" expected_new_comment_text = """New comment on Add-on "{extension.name}"
{some_user.full_name} commented on Add-on "{extension.name}" {some_user.username} commented on Add-on "{extension.name}"
: :
this is bad this is bad
@ -258,7 +258,7 @@ https://extensions.local:8111/
""" """
expected_rated_text = """Add-on rated: "{extension.name}" expected_rated_text = """Add-on rated: "{extension.name}"
{some_user.full_name} rated extension Add-on "{extension.name}" {some_user.username} rated extension Add-on "{extension.name}"
: :
rating text rating text
@ -273,7 +273,7 @@ https://extensions.local:8111/
""" """
expected_review_requested_text = """Add-on review requested: "Edit Breakdown" expected_review_requested_text = """Add-on review requested: "Edit Breakdown"
{some_user.full_name} requested review of Add-on "Edit Breakdown" {some_user.username} requested review of Add-on "Edit Breakdown"
: :
Extension is ready for initial review Extension is ready for initial review

View File

@ -16,7 +16,12 @@ class NotificationsView(LoginRequiredMixin, ListView):
paginate_by = 20 paginate_by = 20
def get_queryset(self): def get_queryset(self):
return Notification.objects.filter(recipient=self.request.user).order_by('-id') return (
Notification.objects.filter(recipient=self.request.user)
.select_related('action')
.prefetch_related('action__action_object', 'action__actor', 'action__target')
.order_by('-id')
)
class MarkReadAllView(LoginRequiredMixin, FormView): class MarkReadAllView(LoginRequiredMixin, FormView):

View File

@ -15,7 +15,7 @@ Django==4.2.11
dj-database-url==1.0.0 dj-database-url==1.0.0
django-activity-stream==2.0.0 django-activity-stream==2.0.0
django-admin-rangefilter==0.8.5 django-admin-rangefilter==0.8.5
django-background-tasks-updated @ git+https://projects.blender.org/infrastructure/django-background-tasks.git@2e60c4ec2fd1e7155bc3f041e0ea4875495a476b django-background-tasks-updated @ git+https://projects.blender.org/infrastructure/django-background-tasks.git@2cbe547fcf183354c80dfd848537f55fc05bf1d5
django-compat==1.0.15 django-compat==1.0.15
django-extended-choices==1.3.3 django-extended-choices==1.3.3
django-loginas==0.3.10 django-loginas==0.3.10

View File

@ -12,11 +12,13 @@
</div> </div>
</td> </td>
<td> <td>
{% include "extensions/components/authors.html" %} <div class="d-flex flex-wrap">
{% include "extensions/components/authors.html" %}
{% if extension.team %} {% if extension.team %}
<a class="text-secondary" href="{{ extension.team.get_absolute_url }}">({{ extension.team.name }})</a> &nbsp;<a class="text-secondary" href="{{ extension.team.get_absolute_url }}">({{ extension.team.name }})</a>
{% endif %} {% endif %}
</div>
</td> </td>
<td title="{{ extension.date_created }}">{{ extension.date_created|naturaltime_compact }}</td> <td title="{{ extension.date_created }}">{{ extension.date_created|naturaltime_compact }}</td>
<td class="ext-review-list-activity" colspan="2"> <td class="ext-review-list-activity" colspan="2">
@ -30,7 +32,7 @@
<span class="badge">{{ stats.count }}</span> <span class="badge">{{ stats.count }}</span>
</a> </a>
</td> </td>
<td> <td class="ext-review-list-status">
<a href="{{ extension.get_review_url }}" class="text-decoration-none"> <a href="{{ extension.get_review_url }}" class="text-decoration-none">
{% with last_type=stats.last_type_display|default:"Awaiting Review" %} {% with last_type=stats.last_type_display|default:"Awaiting Review" %}
{% include "common/components/status.html" with label=last_type slug=last_type|slugify object=extension classes="d-block" icon=True %} {% include "common/components/status.html" with label=last_type slug=last_type|slugify object=extension classes="d-block" icon=True %}

View File

@ -7,7 +7,7 @@
<div class="hero-breadcrumbs"> <div class="hero-breadcrumbs">
<a href="{% url 'reviewers:approval-queue' %}"> <a href="{% url 'reviewers:approval-queue' %}">
<i class="i-chevron-left"></i> <i class="i-chevron-left"></i>
<span>{% trans 'All in Queue' %}</span> <span>{% trans 'All in Approval Queue' %}</span>
</a> </a>
</div> </div>
{% endblock hero_breadcrumbs %} {% endblock hero_breadcrumbs %}
@ -21,9 +21,11 @@
<a href="#activity"> <a href="#activity">
{% trans "Activity" %} {% trans "Activity" %}
</a> </a>
{% if extension.latest_version != None %}
<a href="{{ extension.get_versions_url }}" class="{% if '/versions/' in request.get_full_path %}is-active{% endif %}"> <a href="{{ extension.get_versions_url }}" class="{% if '/versions/' in request.get_full_path %}is-active{% endif %}">
{% trans "Version History" %} {% trans "Version History" %}
</a> </a>
{% endif %}
</div> </div>
<div class="btn-row hero-tabs-admin mb-3 mb-md-0"> <div class="btn-row hero-tabs-admin mb-3 mb-md-0">
{% if is_maintainer %} {% if is_maintainer %}

View File

@ -23,20 +23,17 @@ STATUS_CHANGE_TYPES = [
class ApprovalQueueView(ListView): class ApprovalQueueView(ListView):
model = Extension model = Extension
paginate_by = 100 paginate_by = 50
def get_queryset(self): def get_queryset(self):
qs = ( qs = ApprovalActivity.objects.prefetch_related(
ApprovalActivity.objects.prefetch_related( 'extension',
'extension', 'extension__authors',
'extension__authors', 'extension__icon',
'extension__versions', 'extension__versions',
'extension__versions__file', 'extension__versions__file',
'extension__versions__file__validation', 'extension__versions__file__validation',
) ).order_by('-id')
.order_by('-date_created')
.all()
)
by_extension = {} by_extension = {}
by_date_created = [] by_date_created = []
for item in qs: for item in qs:
@ -88,7 +85,10 @@ class ExtensionsApprovalDetailView(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['review_activity'] = ( ctx['review_activity'] = (
self.object.review_activity.select_related('user').order_by('date_created').all() self.object.review_activity.select_related('user')
.prefetch_related('user__groups')
.order_by('date_created')
.all()
) )
ctx['status_change_types'] = STATUS_CHANGE_TYPES ctx['status_change_types'] = STATUS_CHANGE_TYPES

View File

@ -1,15 +1,21 @@
{% extends 'users/settings/base.html' %} {% extends 'users/settings/base.html' %}
{% block page_title %}Teams{% endblock %}
{% block settings %} {% block settings %}
<h1 class="mb-3">Teams</h1> <h1 class="mb-3">Teams</h1>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<p>
To create a new team or become part of an existing one, please
<a href="https://projects.blender.org/infrastructure/extensions-website/issues/new?template=.gitea/issue_template/team.yaml" target="_blank">get in touch</a>.
</p>
{% if team_memberships %} {% if team_memberships %}
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th class="w-100"> <th class="w-100">
Team name Name
</th> </th>
<th> <th>
Role Role
@ -61,9 +67,6 @@
You are not assigned to any teams yet. You are not assigned to any teams yet.
</p> </p>
{% endif %} {% endif %}
<p class="pt-3">
We can help you with team management if you <a href="https://projects.blender.org/infrastructure/extensions-website/issues/new?title=Team%20Management%20Request&body=Please%20add%20user%20X%20to%20team%20Y">submit your request</a> to the issue tracker.
</p>
</div> </div>
</div> </div>
{% endblock settings %} {% endblock settings %}

View File

@ -54,7 +54,7 @@ class User(TrackChangesMixin, AbstractUser):
is_subscribed_to_notification_emails = models.BooleanField(null=False, default=True) is_subscribed_to_notification_emails = models.BooleanField(null=False, default=True)
def __str__(self) -> str: def __str__(self) -> str:
return f'{self.full_name or self.username}' return self.username
@property @property
def image_url(self) -> Optional[str]: def image_url(self) -> Optional[str]:
@ -142,4 +142,7 @@ class User(TrackChangesMixin, AbstractUser):
@property @property
def is_moderator(self): def is_moderator(self):
# Used to review and approve extensions # Used to review and approve extensions
return self.groups.filter(name='moderators').exists() for g in self.groups.all():
if g.name == 'moderators':
return True
return False

View File

@ -1,7 +1,5 @@
{% load static %} {% load static %}
<img src="{% if user.image %}{{ user.image.url }}{% else %}{% static 'common/images/blank-profile-pic.png' %}{% endif %}" class="profile-avatar {{ classes }}"> <img src="{% if user.image %}{{ user.image.url }}{% else %}{% static 'common/images/blank-profile-pic.png' %}{% endif %}" class="profile-avatar {{ classes }}">
{% if show_name %} {% if show_name %}
<span class="ms-2"> <span class="ms-2">{{ user.username }}</span>
{% firstof user.full_name user.username %}
</span>
{% endif %} {% endif %}

View File

@ -1,8 +1,9 @@
{% extends 'users/settings/base.html' %} {% extends 'users/settings/base.html' %}
{% block page_title %}Delete Account{% endblock %}
{% block settings %} {% block settings %}
{% with can_be_deleted=user.can_be_deleted %} {% with can_be_deleted=user.can_be_deleted %}
<h1 class="mb-3">Delete account</h1> <h1 class="mb-3">Delete Account</h1>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div> <div>

View File

@ -1,6 +1,6 @@
{% extends 'users/settings/base.html' %} {% extends 'users/settings/base.html' %}
{% load static %} {% load static %}
{% block page_title %}Profile{% endblock %}
{% block settings %} {% block settings %}
<h1 class="mb-3">Profile</h1> <h1 class="mb-3">Profile</h1>
@ -15,9 +15,9 @@
<div class="row"> <div class="row">
<div class="col-md-6 mb-4"> <div class="col-md-6 mb-4">
<label class="mb-2 h3" for="userFullName">Full Name</label> <label class="h3 mb-2" for="username">Username</label>
<div class="align-items-center d-flex position-relative"> <div class="align-items-center d-flex position-relative">
<input disabled class="form-control" type="text" value="{{ user.full_name }}" id="userFullName"> <input disabled class="form-control" type="text" value="{{ user.username }}" id="username">
<i class="i-lock"></i> <i class="i-lock"></i>
</div> </div>
<p class="helptext mb-0"> <p class="helptext mb-0">
@ -55,17 +55,6 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6 mb-4">
<label class="h3 mb-2" for="username">Username</label>
<div class="align-items-center d-flex position-relative">
<input disabled class="form-control" type="text" value="{{ user.username }}" id="username">
<i class="i-lock"></i>
</div>
<p class="helptext mb-0">
Displayed in extension pages, rating comments and so on.
</p>
<div class="border-bottom-tertiary mt-3"></div>
</div>
<div class="col-md-6 mb-4"> <div class="col-md-6 mb-4">
<h3 class="label mb-2">Profile Picture</h3> <h3 class="label mb-2">Profile Picture</h3>
<img src="{{ user.image_url }}" width="44" height="44" class="rounded" alt="Profile Image"> <img src="{{ user.image_url }}" width="44" height="44" class="rounded" alt="Profile Image">
@ -74,9 +63,7 @@
</p> </p>
<div class="border-bottom-tertiary mt-3"></div> <div class="border-bottom-tertiary mt-3"></div>
</div> </div>
</div>
<div class="row">
{# TODO: check badges display #} {# TODO: check badges display #}
{% if user.badges %} {% if user.badges %}
<div class="col-md-6 mb-4"> <div class="col-md-6 mb-4">
@ -94,7 +81,9 @@
<div class="border-bottom-tertiary mt-3"></div> <div class="border-bottom-tertiary mt-3"></div>
</div> </div>
{% endif %} {% endif %}
</div>
<div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h3 class="label mb-2">Preview</h3> <h3 class="label mb-2">Preview</h3>
<div class="p-2 background-color rounded"> <div class="p-2 background-color rounded">