Extensions list: sort_by parameter #159
12
.gitea/issue_template/bug.yaml
Normal file
12
.gitea/issue_template/bug.yaml
Normal 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
|
34
.gitea/issue_template/team.yaml
Normal file
34
.gitea/issue_template/team.yaml
Normal 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
1
.gitignore
vendored
@ -68,6 +68,7 @@ wheels/
|
|||||||
*.egg
|
*.egg
|
||||||
*.manifest
|
*.manifest
|
||||||
*.spec
|
*.spec
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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(
|
||||||
|
@ -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 %}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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 %}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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']
|
|
||||||
|
@ -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”
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -12,11 +12,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
{% include "extensions/components/authors.html" %}
|
{% 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>
|
<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 %}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 %}
|
||||||
|
@ -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
|
||||||
|
@ -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 %}
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
Loading…
Reference in New Issue
Block a user