Intitial teams support #147

Merged
Oleg-Komarov merged 34 commits from teams-support into main 2024-05-23 19:43:54 +02:00
54 changed files with 641 additions and 343 deletions
Showing only changes of commit 7dfd6df788 - Show all commits

View File

@ -45,6 +45,7 @@ class AbuseReportTypeFilter(admin.SimpleListFilter):
class AbuseReportAdmin(CommaSearchInAdminMixin, admin.ModelAdmin): class AbuseReportAdmin(CommaSearchInAdminMixin, admin.ModelAdmin):
save_on_top = True
view_on_site = True view_on_site = True
actions = ('delete_selected', 'mark_as_valid', 'mark_as_suspicious') actions = ('delete_selected', 'mark_as_valid', 'mark_as_suspicious')
date_hierarchy = 'date_modified' date_hierarchy = 'date_modified'

@ -1 +1 @@
Subproject commit ffcc72b5cb153fc2a409c795adca82e350655aa2 Subproject commit 6a52e5abcc118133b8cb51137b34bf856da716c4

View File

@ -330,3 +330,6 @@ ACTSTREAM_SETTINGS = {
# Require file validation for other file processing (e.g. thumbnails). # Require file validation for other file processing (e.g. thumbnails).
# Should be set for staging/production. # Should be set for staging/production.
REQUIRE_FILE_VALIDATION = os.getenv('REQUIRE_FILE_VALIDATION', False) REQUIRE_FILE_VALIDATION = os.getenv('REQUIRE_FILE_VALIDATION', False)
# Maximum number of attempts for failing background tasks
MAX_ATTEMPTS = 5

View File

@ -1,25 +1,49 @@
(function() { (function() {
// Create function agreeWithTerms // Create function agreeWithTerms
function agreeWithTerms() { function agreeWithTerms() {
const agreeWithTermsInput = document.querySelector('.js-agree-with-terms-input'); const agreeWithTermsTrigger = document.querySelectorAll('.js-agree-with-terms-trigger');
const agreeWithTermsCheckbox = document.querySelector('.js-agree-with-terms-checkbox');
const agreeWithTermsFileInput = document.querySelector('.js-submit-form-file-input');
let agreeWithTermsBtnSubmit = document.querySelector('.js-agree-with-terms-btn-submit');
if (!agreeWithTermsInput) { if (!agreeWithTermsCheckbox) {
// Stop function execution if agreeWithTermsInput is not present // Stop function execution if agreeWithTermsCheckbox is not present
return; return;
} }
agreeWithTermsInput.addEventListener('change', function(e) { // Both the file input and checkbox can trigger the submit button.
const agreeWithTermsBtnSubmit = document.querySelector('.js-agree-with-terms-btn-submit'); agreeWithTermsTrigger.forEach((el) => {
el.addEventListener('change', function() {
// Check if checkbox is checked // Check if checkbox is checked, and file input has a file selected.
if (e.target.checked == true) { let is_allowed = (agreeWithTermsCheckbox.checked == true) && (agreeWithTermsFileInput.value != "");
if (is_allowed) {
agreeWithTermsBtnSubmit.removeAttribute('disabled'); agreeWithTermsBtnSubmit.removeAttribute('disabled');
} else { } else {
agreeWithTermsBtnSubmit.setAttribute('disabled', true); agreeWithTermsBtnSubmit.setAttribute('disabled', true);
} }
}); });
});
} }
// Create function submitFormFileInputClear
// When the user chooses a new file to upload, clear the error classes.
function submitFormFileInputClear() {
const submitFormFileInput = document.querySelector('.js-submit-form-file-input');
if (!submitFormFileInput) {
// Stop function execution if submitFormFileInput is not present
return;
}
submitFormFileInput.addEventListener('change', function(e) {
e.target.classList.remove('is-invalid');
});
}
// Create function btnBack // Create function btnBack
function btnBack() { function btnBack() {
const btnBack = document.querySelectorAll('.js-btn-back'); const btnBack = document.querySelectorAll('.js-btn-back');
@ -32,7 +56,7 @@
}); });
} }
// Create finction commentForm // Create function commentForm
function commentForm() { function commentForm() {
const commentForm = document.querySelector('.js-comment-form'); const commentForm = document.querySelector('.js-comment-form');
if (!commentForm) { if (!commentForm) {
@ -109,14 +133,36 @@
}); });
} }
init(); init();
} }
// Create function navGlobalLinkSearch
function navGlobalLinkSearch() {
const navGlobalLinkSearch = document.querySelector('.js-nav-global-link-search');
const navGlobalLinkSearchToggle = document.querySelector('.js-nav-global-link-search-toggle');
// Toggle navbar search on small screens
navGlobalLinkSearchToggle.addEventListener('click', function() {
this.classList.toggle('is-active');
if (this.classList.contains('is-active')) {
// Show navGlobalLinkSearch
navGlobalLinkSearch.classList.add('is-active');
} else {
navGlobalLinkSearch.classList.remove('is-active');
}
});
}
// Create function init // Create function init
function init() { function init() {
agreeWithTerms(); agreeWithTerms();
submitFormFileInputClear();
btnBack(); btnBack();
commentForm(); commentForm();
copyInstallUrl(); copyInstallUrl();
navGlobalLinkSearch();
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {

View File

@ -1,16 +1,4 @@
function galleriaCloneFirstItem() { /* Set the image and attributes of the large preview element. */
const galleriaCarrousel = document.getElementById('galleria-items');
if (!galleriaCarrousel) { return;}
let firstGalleriaItem = galleriaCarrousel.firstElementChild.cloneNode(true);
firstGalleriaItem.classList.remove('js-galleria-item-preview', 'is-active');
firstGalleriaItem.classList.add('js-expand-on-click');
firstGalleriaItem.id = 'galleria-item-lg';
document.getElementById("galleria-container").prepend(firstGalleriaItem);
}
function galleriaSetLargePreview(item) { function galleriaSetLargePreview(item) {
let previewsContainer = document.getElementById('galleria-items'); let previewsContainer = document.getElementById('galleria-items');
let previewLarge = document.getElementById('galleria-item-lg'); let previewLarge = document.getElementById('galleria-item-lg');
@ -24,8 +12,9 @@ function galleriaSetLargePreview(item) {
const galleriaContentType = item.dataset.galleriaContentType; const galleriaContentType = item.dataset.galleriaContentType;
const galleriaVideoUrl = item.dataset.galleriaVideoUrl; const galleriaVideoUrl = item.dataset.galleriaVideoUrl;
previewLarge.href = item.href;
previewLarge.classList = item.classList; previewLarge.classList = item.classList;
previewLarge.firstElementChild.src = galleryItem.src; previewLarge.firstElementChild.src = item.href;
previewLarge.firstElementChild.alt = galleryItem.alt; previewLarge.firstElementChild.alt = galleryItem.alt;
previewLarge.dataset.galleriaIndex = galleriaIndex; previewLarge.dataset.galleriaIndex = galleriaIndex;
previewLarge.dataset.galleriaContentType = galleriaContentType; previewLarge.dataset.galleriaContentType = galleriaContentType;
@ -36,6 +25,22 @@ function galleriaSetLargePreview(item) {
} }
/* Create the large preview by cloning the first thumbnail. */
function galleriaCloneFirstItem() {
const galleriaCarrousel = document.getElementById('galleria-items');
if (!galleriaCarrousel) { return;}
let firstGalleriaItem = galleriaCarrousel.firstElementChild.cloneNode(true);
document.getElementById("galleria-container").prepend(firstGalleriaItem);
firstGalleriaItem.classList.remove('js-galleria-item-preview', 'is-active');
firstGalleriaItem.classList.add('js-expand-on-click');
firstGalleriaItem.id = 'galleria-item-lg';
galleriaSetLargePreview(firstGalleriaItem);
}
function galleriaCreateCaption(captionText, overlay) { function galleriaCreateCaption(captionText, overlay) {
let captionContainer = document.getElementById('galleria-caption'); let captionContainer = document.getElementById('galleria-caption');
@ -70,7 +75,7 @@ function galleriaScrollNavigation() {
}); });
} }
/* Create the overlay that will host the image and navigation controls. */
function galleriaCreateOverlay() { function galleriaCreateOverlay() {
let overlay = document.createElement("div"); let overlay = document.createElement("div");
overlay.classList.add("galleria"); overlay.classList.add("galleria");
@ -79,7 +84,7 @@ function galleriaCreateOverlay() {
return overlay; return overlay;
} }
/* Close and delete the overlay. */
function galleriaCloseOverlay(overlay) { function galleriaCloseOverlay(overlay) {
if (overlay.parentNode === document.body) { if (overlay.parentNode === document.body) {
document.body.removeChild(overlay); document.body.removeChild(overlay);
@ -87,7 +92,7 @@ function galleriaCloseOverlay(overlay) {
} }
} }
/* Create the backdrop behind the overlay. */
function galleriaCreateUnderlay() { function galleriaCreateUnderlay() {
let underlay = document.createElement("div"); let underlay = document.createElement("div");
underlay.classList.add("underlay"); underlay.classList.add("underlay");
@ -103,7 +108,7 @@ function galleriaCreateLoadingPlaceholder() {
} }
/* Create Image element. */ /* Create expanded image element inside overlay. */
function galleriaCreateMediaImage(galleriaItem, overlay, loadingPlaceholder) { function galleriaCreateMediaImage(galleriaItem, overlay, loadingPlaceholder) {
let galleriaNewItem = new Image(); let galleriaNewItem = new Image();
galleriaNewItem.id = 'galleria-active-item'; galleriaNewItem.id = 'galleria-active-item';
@ -114,7 +119,7 @@ function galleriaCreateMediaImage(galleriaItem, overlay, loadingPlaceholder) {
} }
}; };
galleriaNewItem.src = galleriaItem.firstElementChild.src; galleriaNewItem.src = galleriaItem.href;
galleriaNewItem.alt = galleriaItem.firstElementChild.alt; galleriaNewItem.alt = galleriaItem.firstElementChild.alt;
galleriaCreateCaption(galleriaNewItem.alt, overlay); galleriaCreateCaption(galleriaNewItem.alt, overlay);
@ -142,7 +147,7 @@ function galleriaCreateMediaVideo(galleriaItem, overlay, loadingPlaceholder) {
galleriaCreateCaption(galleriaItem.firstElementChild.alt, overlay); galleriaCreateCaption(galleriaItem.firstElementChild.alt, overlay);
} }
/* Create expanded media element inside overlay. */
function galleriaCreateMedia(galleriaItem, galleriaContentType, overlay) { function galleriaCreateMedia(galleriaItem, galleriaContentType, overlay) {
const activeItem = overlay.querySelector('#galleria-active-item'); const activeItem = overlay.querySelector('#galleria-active-item');
const loadingPlaceholder = galleriaCreateLoadingPlaceholder(); const loadingPlaceholder = galleriaCreateLoadingPlaceholder();
@ -157,7 +162,6 @@ function galleriaCreateMedia(galleriaItem, galleriaContentType, overlay) {
galleriaCreateMediaImage(galleriaItem, overlay, loadingPlaceholder); galleriaCreateMediaImage(galleriaItem, overlay, loadingPlaceholder);
} }
overlay.appendChild(loadingPlaceholder); overlay.appendChild(loadingPlaceholder);
} }

View File

@ -4,14 +4,17 @@
z-index: 0 z-index: 0
> li > li
--border-color: var(--box-bg-color)
position: relative position: relative
&:only-child
/* Remove vertical line if only item. */
.activity-item:before
display: none
&:first-child &:first-child
/* Remove top half of the vertical line for first item. */ /* Remove top half of the vertical line for first item. */
.activity-status-change:before .activity-item:before
height: 50% top: 3rem
top: 50%
&:last-child &:last-child
.activity-status-change:before .activity-status-change:before
@ -109,7 +112,7 @@
font-size: var(--fs-sm) font-size: var(--fs-sm)
height: var(--spacer-4) height: var(--spacer-4)
justify-content: center justify-content: center
left: -.66rem left: -1rem
position: absolute position: absolute
width: var(--spacer-4) width: var(--spacer-4)

View File

@ -7,9 +7,9 @@
.badge-notifications-count .badge-notifications-count
background-color: var(--color-accent) background-color: var(--color-accent)
border-color: var(--nav-global-color-bg) border-color: var(--bwa-color-bg-primary)
border-radius: var(--spacer-2) border-radius: var(--spacer-2)
color: var(--nav-global-color-text-active) color: var(--bwa-color-text-primary)
display: flex display: flex
font-size: .8rem font-size: .8rem
+fw-bold +fw-bold
@ -17,7 +17,7 @@
left: 1.8rem left: 1.8rem
min-width: var(--spacer) min-width: var(--spacer)
position: absolute position: absolute
top: .6rem top: .2rem
a.badge-tag a.badge-tag
--badge-color: var(--color-text-secondary) --badge-color: var(--color-text-secondary)
@ -35,13 +35,38 @@ a.badge-tag
font-size: var(--fs-xs) font-size: var(--fs-xs)
.badge-status .badge-status
&-approved &-approved,
&-resolved
@extend .badge-success @extend .badge-success
&-awaiting-review &-awaiting-review
@extend .badge-info @extend .badge-info
&-incomplete,
&-awaiting-changes, &-awaiting-changes,
&-draft,
&-untriaged,
@extend .badge-warning @extend .badge-warning
&-disabled-by-staff, &-disabled-by-staff,
&-disabled-by-author &-disabled-by-author
@extend .badge-secondary @extend .badge-secondary
&-confirmed,
&-deleted
@extend .badge-danger
.badge-outline
background-color: transparent
&.badge-status
&-approved,
&-resolved
color: var(--color-success)
&-awaiting-review
color: var(--color-info)
&-awaiting-changes,
&-draft,
&-untriaged,
color: var(--color-warning)
&-disabled-by-staff,
&-disabled-by-author
color: var(--color-secondary)
&-confirmed,
&-deleted
color: var(--color-danger)

View File

@ -1,5 +1,6 @@
.box-outline .box-outline
@extend .box @extend .box
background-color: transparent background-color: transparent
border: var(--border-width) solid var(--border-color) border: var(--border-width) solid var(--border-color)
box-shadow: none box-shadow: none

View File

@ -16,14 +16,23 @@
overflow: initial overflow: initial
text-shadow: none text-shadow: none
h1
margin-left: calc(var(--spacer-3))
.hero-content .hero-content
margin: auto 0 margin: auto 0
.hero-subtitle .hero-subtitle
max-width: none max-width: none
+media-sm
margin-left: calc(var(--spacer-4) + var(--spacer-1) + var(--fs-hero-title))
.badge .badge
+margin(2, right) pointer-events: none
+media-sm
+margin(2, left)
.hero-overlay .hero-overlay
background-color: transparent background-color: transparent
@ -40,6 +49,20 @@
--color-danger-bg: hsl(0deg, 25%, 28%) --color-danger-bg: hsl(0deg, 25%, 28%)
--color-danger-bg-hover: hsl(0deg, 25%, 32%) --color-danger-bg-hover: hsl(0deg, 25%, 32%)
/* Scrollbar. */
/* Style scrollbar in Chromium browsers. */
::-webkit-scrollbar
height: 0.2rem
width: 0.2rem
&-thumb
background-color: var(--color-text-secondary)
border-radius: 0.1rem
&-track
background-color: transparent
border-radius: 0.1rem
/* Site-wide annoncements */ /* Site-wide annoncements */
.site-announcement-alpha .site-announcement-alpha
@extend .alert @extend .alert
@ -230,11 +253,17 @@
font-size: var(--fs-sm) font-size: var(--fs-sm)
line-height: var(--lh-sm) line-height: var(--lh-sm)
ul .details-buttons
+list-unstyled align-items: baseline
margin: 0 display: flex
+padding(2, top) gap: var(--spacer-2)
@extend .list-inline justify-content: space-between
.btn-row
flex-wrap: nowrap
.btn
+padding(2, x)
.drag-widget .drag-widget
align-items: center align-items: center
@ -247,9 +276,18 @@
pointer-events: none pointer-events: none
user-select: none user-select: none
.form-control .ext-preview-thumbnail-icon
&[type="file"] align-items: center
max-width: 50% display: flex
font-size: var(--fs-h2)
justify-content: center
color: var(--color-text-tertiary)
&:hover i
color: var(--color-text-secondary)
i
transition: color var(--transition-speed)
.ext-version-history .ext-version-history
summary summary
@ -332,6 +370,12 @@
.badge .badge
text-decoration: none !important text-decoration: none !important
.ext-review-list-name
display: flex
.extension-icon
+margin(2, right)
.ext-review-list-type .ext-review-list-type
max-width: 9ch max-width: 9ch
min-width: 9ch min-width: 9ch
@ -384,7 +428,16 @@ a
pointer-events: none pointer-events: none
.extension-icon .extension-icon
width: var(--fs-h1) display: inline-block
vertical-align: bottom
width: var(--fs-lg)
img
border-radius: calc(var(--border-radius) / 2)
max-width: 100%
&.icon-lg
width: var(--fs-hero-title)
.icon-preview, .featured-image-preview .icon-preview, .featured-image-preview
height: 9rem height: 9rem

View File

@ -10,6 +10,10 @@
+margin(2, left) +margin(2, left)
.form-control-sm .form-control-sm
font-size: var(--fs-sm)
height: calc(var(--spacer) * 2)
+padding(0, y)
&[type="file"] &[type="file"]
font-size: var(--fs-xs) font-size: var(--fs-xs)
height: calc(var(--spacer) * 2) height: calc(var(--spacer) * 2)
@ -22,3 +26,8 @@
.was-validated .form-control:invalid, .was-validated .form-control:invalid,
.form-control.is-invalid .form-control.is-invalid
background-image: none background-image: none
select
&.form-control
&:hover
cursor: pointer

View File

@ -0,0 +1,13 @@
.dropdown-menu-filter-sort
max-height: calc(var(--spacer) * 24.25)
overflow: auto
.navbar-search
input
color: var(--bwa-color-text)
min-width: calc(var(--spacer) * 4)
&:active,
&:focus,
&:hover
color: var(--bwa-color-text)

View File

@ -1,44 +1,42 @@
.nav-global // TODO: refactor style partial
--nav-global-border-radius: var(--border-radius) .nav-global .nav-global-nav-links
--nav-global-border-radius-lg: var(--border-radius-lg) +padding(2, right)
--nav-global-button-height: calc(var(--spacer) * 2.5)
--nav-global-font-size: var(--fs-base)
--nav-global-link-padding-y: var(--nav-global-spacer-xs);
--nav-global-navbar-height: var(--navbar-primary-height, var(--spacer-6));
--nav-global-spacer: var(--spacer-2)
--nav-global-spacer-sm: var(--spacer-2)
--nav-global-spacer-xs: var(--spacer-1)
.btn .nav-global .nav-global-link-search-toggle
line-height: calc(var(--spacer) * 2) display: none
&:hover // Media query comes from partial navigation_global, where it is explicitly set
background-color: var(--nav-global-color-button-bg-hover) @media (max-width: 767px)
color: var(--nav-global-color-text-hover) !important .nav-global .nav-global-nav-links li a:hover,
.nav-global .nav-global-nav-links li a.nav-global-link-active
background-color: var(--bwa-color-accent-bg) !important
color: var(--bwa-color-accent) !important
.btn-primary .nav-global .nav-global-link-search-toggle
color: var(--color-accent) !important display: flex
input, &.is-active
.form-control background-color: var(--bwa-btn-color-bg-hover)
height: var(--nav-global-button-height) color: var(--bwa-color-text-primary)
.nav-global-nav-links .nav-global-link-search
width: auto display: none !important
+media-md &.is-active
.nav-global background-color: var(--bwa-color-bg-tertiary)
--nav-global-spacer: calc(var(--spacer) * .75) display: inline-flex !important
--nav-global-font-size: var(--fs-sm) left: 0
position: absolute
top: calc(var(--spacer) * 4)
width: 100%
/* Match nav global links dropdown styles with component dropdown menu */ search
/* TODO: @web-assets simplify components navigation */ +padding(3, x)
// #nav-global-nav-links width: 100%
// @extend .dropdown-menu
// .navbar-search
// li max-width: none
// a
// @extend .dropdown-item +media-xl
// .nav-global .nav-global-container
// +media-md max-width: 1320px
// display: inline-flex !important

View File

@ -31,9 +31,17 @@
overflow: hidden overflow: hidden
padding: 0 !important padding: 0 !important
@for $i from 1 through 5
.mw-#{$i}
min-width: var(--spacer-#{$i})
.opacity-50 .opacity-50
opacity: 0.5 opacity: 0.5
@for $i from 1 through 5
.rounded-#{$i}
border-radius: var(--spacer-#{$i})
.show .show
opacity: 1 opacity: 1
@ -44,6 +52,9 @@
pre pre
+margin(3, bottom) +margin(3, bottom)
p:last-child
margin-bottom: 0
.text-accent .text-accent
color: var(--color-accent) color: var(--color-accent)

View File

@ -29,6 +29,7 @@ $container-width: map-get($container-max-widths, 'xl')
@import '_galleria.sass' @import '_galleria.sass'
@import '_hero.sass' @import '_hero.sass'
@import '_list.sass' @import '_list.sass'
@import '_navigation.sass'
@import '_navigation_global.sass' @import '_navigation_global.sass'
@import '_notifications.sass' @import '_notifications.sass'
@import '_table.sass' @import '_table.sass'
@ -39,15 +40,6 @@ $container-width: map-get($container-max-widths, 'xl')
\:root \:root
--z-index-galleria: 1050 --z-index-galleria: 1050
.navbar-search-helper
max-width: 16.0rem
min-width: 6.0rem
/* TODO: temporarily here until it can be moved to web-assets v2. */
.nav-global-links-right
gap: 0 var(--spacer-2)
.profile-avatar .profile-avatar
border-radius: 50% border-radius: 50%
height: var(--spacer-4) height: var(--spacer-4)

View File

@ -52,7 +52,7 @@
<div class="nav-global"> <div class="nav-global">
<div class="nav-global-container"> <div class="nav-global-container">
<nav> <nav>
<div class="site-beta-logo-container text-nowrap"> <div class="d-md-block d-none site-beta-logo-container text-nowrap">
<a href="/" class="nav-global-logo{% if request.get_full_path == '/' %} is-active{% endif %}"> <a href="/" class="nav-global-logo{% if request.get_full_path == '/' %} is-active{% endif %}">
<svg fill-rule="nonzero" viewBox="0 0 200 162.05"> <svg fill-rule="nonzero" viewBox="0 0 200 162.05">
<path <path
@ -69,77 +69,72 @@
{% endswitch %} {% endswitch %}
</div> </div>
<button class="align-items-center d-flex d-md-none nav-global-logo js-dropdown-toggle" data-toggle-menu-id="nav-global-nav-links"> <button class="nav-global-logo js-dropdown-toggle" data-toggle-menu-id="nav-global-nav-links">
<svg class="me-2" fill-rule="nonzero" viewBox="0 0 200 162.05"> <svg fill-rule="nonzero" viewBox="0 0 200 162.05">
<path <path
d="M61.1 104.56c.05 2.6.88 7.66 2.12 11.61a61.27 61.27 0 0 0 13.24 22.92 68.39 68.39 0 0 0 23.17 16.64 74.46 74.46 0 0 0 30.42 6.32 74.52 74.52 0 0 0 30.4-6.42 68.87 68.87 0 0 0 23.15-16.7 61.79 61.79 0 0 0 13.23-22.97 58.06 58.06 0 0 0 2.07-25.55 59.18 59.18 0 0 0-8.44-23.1 64.45 64.45 0 0 0-15.4-16.98h.02L112.76 2.46l-.16-.12c-4.09-3.14-10.96-3.13-15.46.02-4.55 3.18-5.07 8.44-1.02 11.75l-.02.02 26 21.14-79.23.08h-.1c-6.55.01-12.85 4.3-14.1 9.74-1.27 5.53 3.17 10.11 9.98 10.14v.02l40.15-.07-71.66 55-.27.2c-6.76 5.18-8.94 13.78-4.69 19.23 4.32 5.54 13.51 5.55 20.34.03l39.1-32s-.56 4.32-.52 6.91zm100.49 14.47c-8.06 8.2-19.34 12.86-31.54 12.89-12.23.02-23.5-4.6-31.57-12.79-3.93-4-6.83-8.59-8.61-13.48a35.57 35.57 0 0 1 2.34-29.25 39.1 39.1 0 0 1 9.58-11.4 44.68 44.68 0 0 1 28.24-9.85 44.59 44.59 0 0 1 28.24 9.77 38.94 38.94 0 0 1 9.58 11.36 35.58 35.58 0 0 1 4.33 14.18 35.1 35.1 0 0 1-1.98 15.05 37.7 37.7 0 0 1-8.61 13.52zm-57.6-27.91a23.55 23.55 0 0 1 8.55-16.68 28.45 28.45 0 0 1 18.39-6.57 28.5 28.5 0 0 1 18.38 6.57 23.57 23.57 0 0 1 8.55 16.67c.37 6.83-2.37 13.19-7.2 17.9a28.18 28.18 0 0 1-19.73 7.79c-7.83 0-14.84-3-19.75-7.8a23.13 23.13 0 0 1-7.19-17.88z" /> d="M61.1 104.56c.05 2.6.88 7.66 2.12 11.61a61.27 61.27 0 0 0 13.24 22.92 68.39 68.39 0 0 0 23.17 16.64 74.46 74.46 0 0 0 30.42 6.32 74.52 74.52 0 0 0 30.4-6.42 68.87 68.87 0 0 0 23.15-16.7 61.79 61.79 0 0 0 13.23-22.97 58.06 58.06 0 0 0 2.07-25.55 59.18 59.18 0 0 0-8.44-23.1 64.45 64.45 0 0 0-15.4-16.98h.02L112.76 2.46l-.16-.12c-4.09-3.14-10.96-3.13-15.46.02-4.55 3.18-5.07 8.44-1.02 11.75l-.02.02 26 21.14-79.23.08h-.1c-6.55.01-12.85 4.3-14.1 9.74-1.27 5.53 3.17 10.11 9.98 10.14v.02l40.15-.07-71.66 55-.27.2c-6.76 5.18-8.94 13.78-4.69 19.23 4.32 5.54 13.51 5.55 20.34.03l39.1-32s-.56 4.32-.52 6.91zm100.49 14.47c-8.06 8.2-19.34 12.86-31.54 12.89-12.23.02-23.5-4.6-31.57-12.79-3.93-4-6.83-8.59-8.61-13.48a35.57 35.57 0 0 1 2.34-29.25 39.1 39.1 0 0 1 9.58-11.4 44.68 44.68 0 0 1 28.24-9.85 44.59 44.59 0 0 1 28.24 9.77 38.94 38.94 0 0 1 9.58 11.36 35.58 35.58 0 0 1 4.33 14.18 35.1 35.1 0 0 1-1.98 15.05 37.7 37.7 0 0 1-8.61 13.52zm-57.6-27.91a23.55 23.55 0 0 1 8.55-16.68 28.45 28.45 0 0 1 18.39-6.57 28.5 28.5 0 0 1 18.38 6.57 23.57 23.57 0 0 1 8.55 16.67c.37 6.83-2.37 13.19-7.2 17.9a28.18 28.18 0 0 1-19.73 7.79c-7.83 0-14.84-3-19.75-7.8a23.13 23.13 0 0 1-7.19-17.88z" />
</svg> </svg>
<strong class="fs-base me-1">Extensions</strong> <strong>Extensions</strong>
<i class="i-chevron-down"></i> <i class="i-chevron-down"></i>
</button> </button>
<ul class="flex-nowrap me-4 nav-global-nav-links nav-global-dropdown js-dropdown-menu" id="nav-global-nav-links"> <ul class="flex-nowrap nav-global-nav-links nav-global-dropdown js-dropdown-menu" id="nav-global-nav-links">
<li class="d-md-none"> <li class="d-md-none">
<a href="/" class="{% if request.get_full_path == '/' %}is-active{% endif %}"> <a href="/" class="{% if request.get_full_path == '/' %}nav-global-link-active{% endif %}">
Home Home
</a> </a>
</li> </li>
<li> <li>
<a href="{% url 'extensions:by-type' type_slug='add-ons' %}" class="{% if '/add-ons/' in request.get_full_path %}is-active{% endif %}"> <a href="{% url 'extensions:by-type' type_slug='add-ons' %}" class="{% if '/add-ons/' in request.get_full_path %}nav-global-link-active{% endif %}">
Add-ons Add-ons
</a> </a>
</li> </li>
<li> <li>
<a href="{% url 'extensions:by-type' type_slug='themes' %}" class="{% if '/themes/' in request.get_full_path %}is-active{% endif %}"> <a href="{% url 'extensions:by-type' type_slug='themes' %}" class="{% if '/themes/' in request.get_full_path %}nav-global-link-active{% endif %}">
Themes Themes
</a> </a>
</li> </li>
<li> <li>
<a href="{% url 'reviewers:approval-queue' %}" class="{% if '/approval-queue/' in request.get_full_path %}is-active{% endif %}"> <a href="{% url 'reviewers:approval-queue' %}" class="{% if '/approval-queue/' in request.get_full_path %}nav-global-link-active{% endif %}">
Approval Queue Approval Queue
</a> </a>
</li> </li>
<li> <li>
<a href="{% url 'flatpage-about' %}" class="{% if '/about/' in request.get_full_path %}is-active{% endif %}"> <a href="{% url 'flatpage-about' %}" class="{% if '/about/' in request.get_full_path %}nav-global-link-active{% endif %}">
About About
</a> </a>
</li> </li>
</ul> </ul>
<ul class="flex-grow-1 flex-nowrap justify-content-end ms-0 nav-global-links-right"> <ul class="nav-global-links-right">
<li> <li class="js-nav-global-link-search nav-global-link-search">
<button class="js-toggle-theme-btn px-2"><i class="js-toggle-theme-btn-icon i-adjust"></i></button> <search>
</li> <form action="{% url 'extensions:search' %}" class="navbar-search" method="GET">
<li class="flex-grow-1 navbar-search-helper"> <input name="q" aria-label="Search" aria-describedby="nav-search-button" class="form-control" type="text" placeholder="Search..." {% if request.GET.q %} value="{{ request.GET.q }}" {% else %} {% endif %}>
<search class="w-100"> <button id="nav-search-button" type="submit">
<form action="{% url "extensions:search" %}" method="GET" class="me-0 ms-0 navbar-search">
<input type="text" name="q" class="form-control"
{% if request.GET.q %}
value="{{ request.GET.q }}"
{% else %}
placeholder="Search..."
{% endif %}
aria-label="Search"
aria-describedby="nav-search-button">
<button type="submit" id="nav-search-button">
<i class="i-search"></i> <i class="i-search"></i>
</button> </button>
</form> </form>
</search> </search>
</li> </li>
<li>
<button class="js-nav-global-link-search-toggle nav-global-link-search-toggle"><i class="i-search"></i></button>
</li>
{% block nav-upload %} {% block nav-upload %}
<li class="d-none d-xl-flex"> <li class="d-lg-inline-flex d-none">
<a href="{% url 'extensions:submit' %}" class="btn btn-primary"> <a class="nav-global-btn nav-global-btn-primary" href="{% url 'extensions:submit' %}"><i class="i-upload"></i><span>Upload Extension</span></a>
<i class="i-upload"></i>
<span>Upload Extension</span>
</a>
</li> </li>
{% endblock nav-upload %} {% endblock nav-upload %}
<li>
<button class="js-toggle-theme-btn"><i class="js-toggle-theme-btn-icon i-adjust"></i></button>
</li>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="nav-item"> <li>
<a class="btn btn-link position-relative px-2" href="{% url 'notifications:notifications' %}"> <a class="nav-global-btn position-relative" href="{% url 'notifications:notifications' %}">
{% with unread_notification_count=user|unread_notification_count %} {% with unread_notification_count=user|unread_notification_count %}
{% if unread_notification_count %} {% if unread_notification_count %}
<div class="badge badge-notifications-count">{{ unread_notification_count }}</div> <div class="badge badge-notifications-count">{{ unread_notification_count }}</div>
@ -148,10 +143,10 @@
<i class="i-bell"></i> <i class="i-bell"></i>
</a> </a>
</li> </li>
<li class="nav-item dropdown"> <li class="dropdown">
<button id="navbarDropdown" aria-expanded="false" aria-haspopup="true" data-toggle-menu-id="nav-account-dropdown" role="button" class="nav-link dropdown-toggle js-dropdown-toggle pe-3 px-2"> <button id="navbarDropdown" aria-expanded="false" aria-haspopup="true" data-toggle-menu-id="nav-account-dropdown" role="button" class="nav-link dropdown-toggle js-dropdown-toggle">
<i class="i-user"></i> <i class="i-user"></i>
<i class="i-chevron-down"></i> <i class="d-none d-md-inline i-chevron-down"></i>
</button> </button>
<ul id="nav-account-dropdown" aria-labelledby="navbarDropdown" class="dropdown-menu dropdown-menu-right js-dropdown-menu"> <ul id="nav-account-dropdown" aria-labelledby="navbarDropdown" class="dropdown-menu dropdown-menu-right js-dropdown-menu">
{% if user.is_staff %} {% if user.is_staff %}
@ -218,10 +213,12 @@
</ul> </ul>
</li> </li>
{% elif page_id != 'login' and page_id != 'register' %} {% elif page_id != 'login' and page_id != 'register' %}
<a href="{% url 'oauth:login' %}" class="btn btn-link"> <li>
<a class="nav-global-btn" href="{% url 'oauth:login' %}">
<i class="i-log-in"></i> <i class="i-log-in"></i>
<span>{% trans "Sign in" %}</span> <span>{% trans "Sign in" %}</span>
</a> </a>
</li>
{% endif %} {% endif %}
<li> <li>
@ -267,6 +264,16 @@
</div> </div>
{% block footer %} {% block footer %}
<div class="footer-pages pt-2">
<div class="container">
<ul>
<li><a href="/about">About</a></li>
<li><a href="https://www.blender.org/privacy-policy">Privacy Policy</a></li>
<li><a href="/terms-of-service">Terms of Service</a></li>
</ul>
</div>
</div>
{% include "_footer.html" %} {% include "_footer.html" %}
{% endblock footer %} {% endblock footer %}

View File

@ -1,26 +1,58 @@
{% load common %} {% load common %}
{% get_proper_elided_page_range page_obj as page_range %} {% get_proper_elided_page_range page_obj as page_range %}
<nav> <nav>
<ul class="pagination"> <ul class="pagination pb-2">
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
{% if page_obj.number != 1 %}
<li class="page-item page-first">
<a href="?{% query_transform page=1 %}">
First
</a>
</li>
{% endif %}
{% if page_obj.has_previous %}
<li class="page-item page-prev">
<a href="?{% query_transform page=page_obj.previous_page_number %}">
<i class="i-chevron-left"></i> Previous
</a>
</li>
{% endif %}
{% for page_number in page_range %} {% for page_number in page_range %}
{% if page_number == '…' %} {% if page_number == '…' %}
<li class="page-item disabled"> <li class="page-item disabled">
<span class="page-link px-0">...</span> <span class="page-link px-0">...</span>
</li> </li>
{% elif page_obj.number == page_number %} {% elif page_obj.number == page_number %}
<li class="page-item active" aria-current="page"> <li class="page-item page-current active" aria-current="page">
<a>{{ page_obj.number }}</a> <a>{{ page_obj.number }}</a>
</li> </li>
{% else %} {% else %}
<li class="page-item"> <li class="page-item">
<a href="?page={{ page_number }}">{{ page_number }}</a> <a href="?{% query_transform page=page_number %}">{{ page_number }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %}
<li class="page-item"> {% if page_obj.has_next %}
{{ page_obj.paginator.count }} {{ label }}{{ page_obj.paginator.count | pluralize }} <li class="page-item page-next">
<a href="?{% query_transform page=page_obj.next_page_number %}">
Next <i class="i-chevron-right"></i>
</a>
</li> </li>
{% endif %}
{% if page_obj.number != page_obj.paginator.num_pages %}
<li class="page-item page-last">
<a href="?{% query_transform page=page_obj.paginator.num_pages %}">
Last
</a>
</li>
{% endif %}
{% endif %}
</ul> </ul>
<div class="page-item text-center">
{{ page_obj.paginator.count }} {{ label }}{{ page_obj.paginator.count | pluralize }}
</div>
</nav> </nav>

View File

@ -1,51 +1,51 @@
{% with status=object.get_status_display %} {% with status=object.get_status_display %}
{% if 'incomplete' in status.lower %} {% if 'draft' in status.lower %}
<div class="badge badge-warning {{ class }}" title="Requires re-uploading or editing"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}" title="Requires re-uploading or editing">
<i class="i-alert-triangle"></i> <i class="i-alert-triangle"></i>
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'disabled' in status.lower %} {% elif 'disabled' in status.lower %}
<div class="badge badge-danger {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<i class="i-eye"></i> <i class="i-eye"></i>
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'deleted' in status.lower %} {% elif 'deleted' in status.lower %}
<div class="badge badge-danger {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<i class="i-eye"></i> <i class="i-eye"></i>
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'awaiting' in status.lower %} {% elif 'awaiting' in status.lower %}
<div class="badge badge-info {{ class }}" title="Awaiting a review by a human being and not yet publicly visible"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}" title="Awaiting a review by a human being and not yet publicly visible">
<i class="i-eye"></i> <i class="i-eye"></i>
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'approved' in status.lower %} {% elif 'approved' in status.lower %}
<div class="badge badge-success {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<i class="i-check"></i> <i class="i-check"></i>
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'confirmed' in status.lower %} {% elif 'confirmed' in status.lower %}
<div class="badge badge-danger {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'untriaged' in status.lower %} {% elif 'untriaged' in status.lower %}
<div class="badge badge-warning {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% elif 'resolved' in status.lower %} {% elif 'resolved' in status.lower %}
<div class="badge badge-success {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% else %} {% else %}
<div class="badge badge-secondary {{ class }}"> <div class="badge badge-status-{{ object.status_slug }} {{ class }}">
<span>{{ status }}</span> <span>{{ status }}</span>
</div> </div>
{% endif %} {% endif %}

View File

@ -29,6 +29,17 @@ def absolute_url(context, path: str) -> str:
return utils.absolutify(path, request=request) return utils.absolutify(path, request=request)
# Allows for example to go to another page of search
# results while keeping the search query.
# Credit: https://stackoverflow.com/questions/46026268/
@register.simple_tag(takes_context=True)
def query_transform(context, **kwargs):
query = context['request'].GET.copy()
for k, v in kwargs.items():
query[k] = v
return query.urlencode()
class PaginationRenderer: class PaginationRenderer:
def __init__(self, pager): def __init__(self, pager):
self.pager = pager self.pager = pager

View File

@ -10,7 +10,7 @@ EXTENSION_TYPE_CHOICES = Choices(
('BPY', 1, _('Add-on')), ('BPY', 1, _('Add-on')),
('THEME', 2, _('Theme')), ('THEME', 2, _('Theme')),
) )
STATUS_INCOMPLETE = 1 STATUS_DRAFT = 1
STATUS_AWAITING_REVIEW = 2 STATUS_AWAITING_REVIEW = 2
STATUS_APPROVED = 3 STATUS_APPROVED = 3
STATUS_DISABLED = 4 STATUS_DISABLED = 4
@ -18,7 +18,7 @@ STATUS_DISABLED_BY_AUTHOR = 5
# Extension statuses # Extension statuses
EXTENSION_STATUS_CHOICES = Choices( EXTENSION_STATUS_CHOICES = Choices(
('INCOMPLETE', STATUS_INCOMPLETE, _('Incomplete')), ('DRAFT', STATUS_DRAFT, _('Draft')),
('AWAITING_REVIEW', STATUS_AWAITING_REVIEW, _('Awaiting Review')), ('AWAITING_REVIEW', STATUS_AWAITING_REVIEW, _('Awaiting Review')),
('APPROVED', STATUS_APPROVED, _('Approved')), ('APPROVED', STATUS_APPROVED, _('Approved')),
('DISABLED', STATUS_DISABLED, _('Disabled by staff')), ('DISABLED', STATUS_DISABLED, _('Disabled by staff')),

View File

@ -34,6 +34,7 @@ class VersionInline(NoAddDeleteMixin, admin.TabularInline):
class ExtensionAdmin(admin.ModelAdmin): class ExtensionAdmin(admin.ModelAdmin):
save_on_top = True
date_hierarchy = 'date_created' date_hierarchy = 'date_created'
list_display = ( list_display = (
'__str__', '__str__',
@ -135,6 +136,7 @@ class ExtensionAdmin(admin.ModelAdmin):
class VersionAdmin(admin.ModelAdmin): class VersionAdmin(admin.ModelAdmin):
save_on_top = True
date_hierarchy = 'date_created' date_hierarchy = 'date_created'
list_display = ( list_display = (
'__str__', '__str__',

View File

@ -246,7 +246,7 @@ class ExtensionUpdateForm(forms.ModelForm):
if self.instance.status != self.instance.STATUSES.AWAITING_REVIEW: if self.instance.status != self.instance.STATUSES.AWAITING_REVIEW:
self.add_error(None, self.msg_cannot_convert_to_draft) self.add_error(None, self.msg_cannot_convert_to_draft)
else: else:
self.instance.status = self.instance.STATUSES.INCOMPLETE self.instance.status = self.instance.STATUSES.DRAFT
self.instance.converted_to_draft = True self.instance.converted_to_draft = True
# Send the extension and version to the review, if possible # Send the extension and version to the review, if possible

View File

@ -29,7 +29,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=255, unique=True)), ('name', models.CharField(max_length=255, unique=True)),
('description', models.TextField(help_text='\n<p><a href="https://commonmark.org/help/" rel="nofollow" target="_blank">Markdown</a>\nis supported.</p>\n')), ('description', models.TextField(help_text='\n<p><a href="https://commonmark.org/help/" rel="nofollow" target="_blank">Markdown</a>\nis supported.</p>\n')),
('tagline', models.CharField(help_text='A very short description', max_length=128)), ('tagline', models.CharField(help_text='A very short description', max_length=128)),
('status', models.PositiveSmallIntegerField(choices=[(1, 'Incomplete'), (2, 'Awaiting Review'), (3, 'Approved'), (4, 'Disabled by staff'), (5, 'Disabled by author')], default=1)), ('status', models.PositiveSmallIntegerField(choices=[(1, 'Draft'), (2, 'Awaiting Review'), (3, 'Approved'), (4, 'Disabled by staff'), (5, 'Disabled by author')], default=1)),
('doc_url', models.URLField(blank=True, help_text='URL of the documentation', null=True)), ('doc_url', models.URLField(blank=True, help_text='URL of the documentation', null=True)),
('tracker_url', models.URLField(blank=True, help_text='URL of the issue tracker', null=True)), ('tracker_url', models.URLField(blank=True, help_text='URL of the issue tracker', null=True)),
('homepage_url', models.URLField(blank=True, help_text='URL of the homepage', null=True)), ('homepage_url', models.URLField(blank=True, help_text='URL of the homepage', null=True)),

View File

@ -20,6 +20,6 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='extension', model_name='extension',
name='icon', name='icon',
field=models.OneToOneField(help_text='A 256 x 256 icon representing this extension.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='icon_of', to='files.file'), field=models.OneToOneField(help_text='A 256 x 256 PNG icon representing this extension.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='icon_of', to='files.file'),
), ),
] ]

View File

@ -184,11 +184,11 @@ class Extension(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Mod
null=True, null=True,
blank=False, blank=False,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
help_text="A 256 x 256 icon representing this extension.", help_text="A 256 x 256 PNG icon representing this extension.",
) )
previews = FilterableManyToManyField('files.File', through='Preview', related_name='extensions') previews = FilterableManyToManyField('files.File', through='Preview', related_name='extensions')
status = models.PositiveSmallIntegerField(choices=STATUSES, default=STATUSES.INCOMPLETE) status = models.PositiveSmallIntegerField(choices=STATUSES, default=STATUSES.DRAFT)
support = models.URLField( support = models.URLField(
help_text='URL for reporting issues or contact details for support.', null=True, blank=True help_text='URL for reporting issues or contact details for support.', null=True, blank=True
) )
@ -384,7 +384,7 @@ class Extension(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Mod
def should_redirect_to_submit_flow(self): def should_redirect_to_submit_flow(self):
return ( return (
self.status == self.STATUSES.INCOMPLETE self.status == self.STATUSES.DRAFT
and not self.has_complete_metadata() and not self.has_complete_metadata()
and self.latest_version is not None and self.latest_version is not None
) )

View File

@ -104,7 +104,7 @@ def _set_is_listed(
return return
if extension.status == extensions.models.Extension.STATUSES.APPROVED and not new_is_listed: if extension.status == extensions.models.Extension.STATUSES.APPROVED and not new_is_listed:
extension.status = extensions.models.Extension.STATUSES.INCOMPLETE extension.status = extensions.models.Extension.STATUSES.DRAFT
logger.info('Extension pk=%s becomes listed', extension.pk) logger.info('Extension pk=%s becomes listed', extension.pk)
extension.is_listed = new_is_listed extension.is_listed = new_is_listed

View File

@ -30,28 +30,25 @@ function appendImageUploadForm() {
<div class="previews-list-item"> <div class="previews-list-item">
<div class="align-items-center d-flex previews-list-item-thumbnail ps-3"> <div class="align-items-center d-flex previews-list-item-thumbnail ps-3">
<div class="js-input-img-thumbnail previews-list-item-thumbnail-img" title="Preview"> <div class="js-input-img-thumbnail previews-list-item-thumbnail-img" title="Preview">
<div class="align-items-center d-flex js-input-img-thumbnail-icon justify-content-center"> <div class="ext-preview-thumbnail-icon js-input-img-thumbnail-icon">
<i class="i-image"></i> <i class="i-image"></i>/<i class="i-reel"></i>
</div> </div>
</div> </div>
</div> </div>
<div class="details flex-grow-1"> <div class="details">
<div class="mb-2"> <div>
<label for="${formsetPrefix}-${i}-caption">Caption</label> <label for="${formsetPrefix}-${i}-caption">Image or Video</label>
<input class="js-input-img-caption form-control" id="${formsetPrefix}-${i}-caption" type="text" maxlength="255" name="${formsetPrefix}-${i}-caption" placeholder="Describe the preview"> <input class="js-input-img-caption form-control" id="${formsetPrefix}-${i}-caption" type="text" maxlength="255" name="${formsetPrefix}-${i}-caption" placeholder="Description">
</div> </div>
<div class="align-items-center d-flex justify-content-between"> <div class="details-buttons">
<input accept="image/jpg,image/jpeg,image/png,image/webp,video/mp4" class="form-control form-control-sm js-input-img" id="id_${formsetPrefix}-${i}-source" type="file" name="${formsetPrefix}-${i}-source"> <input accept="image/jpg,image/jpeg,image/png,image/webp,video/mp4" class="form-control form-control-sm js-input-img" id="id_${formsetPrefix}-${i}-source" type="file" name="${formsetPrefix}-${i}-source">
<ul class="pt-0"> <div class="btn-row">
<li> <button class="btn btn-link btn-sm js-btn-remove-img-upload-form"><i class="i-trash"></i> Delete</button>
<button class="btn btn-link btn-sm js-btn-reset-img-upload-form ps-2 pe-0"><i class="i-refresh"></i> Reset</button> <button class="btn btn-link btn-sm js-btn-reset-img-upload-form"><i class="i-refresh"></i> Reset</button>
</li>
<li>
<button class="btn btn-link btn-sm js-btn-remove-img-upload-form ps-2 pe-0"><i class="i-trash"></i> Delete</button>
</li>
</ul>
</div> </div>
</div> </div>
<div class="form-text">A JPEG, PNG or WebP image, or an MP4 video.</div>
</div>
</div> </div>
`; `;
formRow.classList.add('ext-edit-field-row', 'fade', 'js-ext-edit-field-row'); formRow.classList.add('ext-edit-field-row', 'fade', 'js-ext-edit-field-row');

View File

@ -18,7 +18,7 @@
</div> </div>
{% endblock hero_breadcrumbs %} {% endblock hero_breadcrumbs %}
<h1>{% include "extensions/components/icon.html" %} {{ extension.name }}</h1> <h1>{% include "extensions/components/icon.html" with classes="icon-lg" %} {{ extension.name }}</h1>
<div class="hero-subtitle"> <div class="hero-subtitle">
{% if latest.tagline %} {% if latest.tagline %}
@ -26,12 +26,6 @@
{% endif %} {% endif %}
<div class="ext-detail-authors"> <div class="ext-detail-authors">
{% if not extension.is_approved %}
<a href="{{ extension.get_review_url }}" class="badge badge-status-{{ extension.get_status_display|slugify }}">
{{ extension.get_status_display }}
</a>
{% endif %}
<a href="{% url 'extensions:by-type' type_slug=extension.type_slug %}">{{ extension.get_type_display }}</a> <a href="{% url 'extensions:by-type' type_slug=extension.type_slug %}">{{ extension.get_type_display }}</a>
{% if extension.team %} {% if extension.team %}
@ -43,6 +37,10 @@
{% elif extension.authors.count %} {% elif extension.authors.count %}
{% trans 'by' %} {% include "extensions/components/authors.html" %} {% trans 'by' %} {% include "extensions/components/authors.html" %}
{% endif %} {% endif %}
{% if not extension.is_approved %}
{% include "common/components/status.html" with object=extension class="badge-outline" %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -87,16 +85,7 @@
<i class="i-chevron-down"></i> <i class="i-chevron-down"></i>
</button> </button>
<ul id="extension-admin-menu" class="dropdown-menu dropdown-menu-right js-dropdown-menu"> <ul id="extension-admin-menu" class="dropdown-menu dropdown-menu-right js-dropdown-menu">
<li> {% include "extensions/components/dropdown_admin.html" %}
<a href="{% url 'admin:extensions_extension_change' extension.pk %}" class="dropdown-item is-admin">
<i class="i-edit"></i> {% trans 'Edit' %}
</a>
</li>
<li>
<a href="{% url 'admin:ratings_rating_changelist' %}?extension_id={{ extension.pk }}" class="dropdown-item is-admin">
<i class="i-star"></i> {% trans 'Reviews' %}
</a>
</li>
</ul> </ul>
</div> </div>
{% endif %} {% endif %}

View File

@ -0,0 +1,38 @@
{% load i18n %}
<li>
<a href="{% url 'admin:extensions_extension_change' extension.pk %}" class="dropdown-item is-admin">
<i class="i-edit"></i> {% trans 'Extension' %}
</a>
</li>
{% if extension.latest_version %}
<li>
<a href="{% url 'admin:extensions_version_change' extension.latest_version.pk %}" class="dropdown-item is-admin">
<i class="i-puzzle"></i> {% trans 'Version' %}
</a>
</li>
{% endif %}
<li>
<a href="{% url 'admin:ratings_rating_changelist' %}?extension_id={{ extension.pk }}" class="dropdown-item is-admin">
<i class="i-star"></i> {% trans 'Reviews' %}
</a>
</li>
{% if extension.authors.all.0 %}
<li class="dropdown-divider"></li>
<li>
<a href="{% url 'admin:users_user_change' extension.authors.all.0.pk %}" class="dropdown-item is-admin">
<i class="i-user"></i> {% trans 'Maintainer' %}{{ extension.authors.all.count|pluralize }}
{% if extension.authors.all.count > 1 %}({{ extension.authors.all.count }}){% endif %}
</a>
</li>
{% endif %}
{% if extension.team %}
<li>
<a href="{% url 'admin:teams_team_change' extension.team.pk %}" class="dropdown-item is-admin">
<i class="i-users"></i> {% trans 'Team' %}
</a>
</li>
{% endif %}

View File

@ -4,6 +4,7 @@
<div class="galleria-items{% if preview_count > 5 %} is-many{% endif %}{% if preview_count == 1 %} is-single{% endif %}" id="galleria-items"> <div class="galleria-items{% if preview_count > 5 %} is-many{% endif %}{% if preview_count == 1 %} is-single{% endif %}" id="galleria-items">
{% for preview in previews %} {% for preview in previews %}
{% with thumbnail_1080p_url=preview.file.thumbnail_1080p_url file=preview.file %} {% with thumbnail_1080p_url=preview.file.thumbnail_1080p_url file=preview.file %}
{% with thumbnail_360p_url=preview.file.thumbnail_360p_url file=preview.file %}
<a <a
class="galleria-item js-galleria-item-preview galleria-item-type-{{ file.content_type|slugify|slice:5 }}{% if forloop.first %} is-active{% endif %}" class="galleria-item js-galleria-item-preview galleria-item-type-{{ file.content_type|slugify|slice:5 }}{% if forloop.first %} is-active{% endif %}"
href="{{ thumbnail_1080p_url }}" href="{{ thumbnail_1080p_url }}"
@ -11,9 +12,10 @@
data-galleria-content-type="{{ file.content_type }}" data-galleria-content-type="{{ file.content_type }}"
data-galleria-index="{{ forloop.counter }}"> data-galleria-index="{{ forloop.counter }}">
<img src="{{ thumbnail_1080p_url }}" alt="{{ preview.caption }}"> <img src="{{ thumbnail_360p_url }}" alt="{{ preview.caption }}">
</a> </a>
{% endwith %} {% endwith %}
{% endwith %}
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}

View File

@ -1,3 +1,5 @@
{% load static %} {% load static %}
<img class="extension-icon mb-2 rounded" src="{% if extension.icon.source %}{{ extension.icon.source.url }}{% else %}{% static 'common/images/no-icon.png' %}{% endif %}"> <div class="extension-icon {{ classes}}">
<img alt="{{ extension.get_type_display }} {{ extension.name }}" src="{% if extension.icon.source %}{{ extension.icon.source.url }}{% else %}{% static 'common/images/no-icon.png' %}{% endif %}">
</div>

View File

@ -14,27 +14,25 @@
<div class="col-md-12"> <div class="col-md-12">
<h2> <h2>
{% blocktranslate with type=type|lower %} {% blocktranslate with type=type|lower %}
Submit your {{ type }} for review Submit {{ type }} for approval
{% endblocktranslate %} {% endblocktranslate %}
</h2> </h2>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-8 mt-3"> <div class="col-md-8">
<form id="update-extension-form" method="post" enctype="multipart/form-data"> <form id="update-extension-form" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{% with form=form|add_form_classes extension_form=extension_form|add_form_classes %} {% with form=form|add_form_classes extension_form=extension_form|add_form_classes %}
<section class="mb-4"> <section class="pb-3">
<p> <p>
{% blocktranslate with type=type|lower %} {% blocktranslate with type=type|lower %}
Please check and edit your {{ type }}'s details before submitting it for review. Please check and edit your {{ type }}'s details before submitting it.
{% endblocktranslate %} {% endblocktranslate %}
</p> <br>
<p>
{% blocktranslate with type=type|lower %} {% blocktranslate with type=type|lower %}
After the {{ type }} is approved by the Blender community or Blender Institute's staff, The {{ type }} will be publicly visible once it is approved by the moderation team.
it will become publicly visible on the platform.
{% endblocktranslate %} {% endblocktranslate %}
</p> </p>
</section> </section>
@ -51,21 +49,22 @@
</section> </section>
<section class="mt-4"> <section class="mt-4">
<h2>{% trans 'Featured image and icon' %}</h2> <h2>{% trans 'Media' %}</h2>
<div class="previews-upload"> <div class="row flex">
<div class="row"> <div class="col-md-6">
<div class="col"> <div class="box p-3">
{% trans "Icon" as icon_label %} {% trans "Featured Image" as featured_image_label %}
{% trans "A 256 x 256 icon representing this extension." as icon_help_text %} {% trans "A JPEG, PNG or WebP image, at least 1920 x 1080 and with aspect ratio of 16:9." as featured_image_help_text %}
{% include "extensions/manage/components/set_image.html" with image_form=icon_form label=icon_label help_text=icon_help_text %}
</div>
<div class="col">
{% trans "Featured image" as featured_image_label %}
{% trans "Should have resolution of at least 1920 x 1080 and aspect ratio of 16:9." as featured_image_help_text %}
{% include "extensions/manage/components/set_image.html" with image_form=featured_image_form label=featured_image_label help_text=featured_image_help_text %} {% include "extensions/manage/components/set_image.html" with image_form=featured_image_form label=featured_image_label help_text=featured_image_help_text %}
</div> </div>
</div> </div>
<div class="col-md-6">
<div class="box p-3">
{% trans "Icon" as icon_label %}
{% trans "A 256 x 256 PNG icon representing this extension." as icon_help_text %}
{% include "extensions/manage/components/set_image.html" with image_form=icon_form label=icon_label help_text=icon_help_text %}
</div>
</div>
</div> </div>
</section> </section>

View File

@ -6,60 +6,93 @@
{% block content %} {% block content %}
<div class="row {% if type == 'Add-ons' %}is-row-add-ons{% elif type == 'Themes' %}is-row-themes{% endif %}"> <div class="row {% if type == 'Add-ons' %}is-row-add-ons{% elif type == 'Themes' %}is-row-themes{% endif %}">
<div class="col-md-12 my-4"> <div class="col-md-12 my-4">
{% if tag or type %}
<div class="row mb-3">
<div class="col-md-12 d-flex justify-content-between">
{% if type %}
<h2 class="me-auto">{{ type }}</h2>
{% else %}
<h2 class="align-items-center d-flex mb-0">
<span class="me-3">{% blocktranslate %}Extensions with the tag{% endblocktranslate %}</span>
{% include "extensions/components/badge_tag.html" %}
</h2>
{% endif %}
{% if tags %}
<div class="d-flex flex-column flex-md-row">
<div class="box dropdown me-md-3 p-2 rounded-2">
<button class="align-items-center d-flex dropdown-toggle js-dropdown-toggle" data-toggle-menu-id="js-dropdown-menu-filter">
{% if tag %}
{{ tag.name }}
{# TODO: @back-end add tags count dynamic #}
{% comment %}
<div class="align-items-center bg-secondary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
1
</div>
{% endcomment %}
{% else %}
All
{# TODO: @back-end add tags count dynamic #}
{% comment %}
<div class="align-items-center bg-secondary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
1
</div>
{% endcomment %}
{% endif %}
<i class="i-chevron-down"></i>
</button>
<ul class="dropdown-menu dropdown-menu-filter-sort dropdown-menu-right js-dropdown-menu" id="js-dropdown-menu-filter">
<li>
{% if tag %}
{# If tag is active, show button 'All'. #}
{# TODO @back-end: Find a proper way to get the plural tag type to build the URL. #}
<a class="dropdown-item justify-content-between" href="/{{ tag.get_type_display|slugify }}s/">
All
{% comment %}
<div class="align-items-center bg-secondary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
1
</div>
{% endcomment %}
</a>
{% endif %}
</li>
{% for list_tag in tags %}
<li>
<a class="dropdown-item justify-content-between" href="{% url "extensions:by-tag" tag_slug=list_tag.slug %}" title="{{ list_tag.name }}">
{{ list_tag.name }}
{% comment %}
<div class="align-items-center bg-secondary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
1
</div>
{% endcomment %}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
</div>
{% else %}
{% if author %} {% if author %}
<h2>{% blocktranslate %}Extensions by{% endblocktranslate %} <em class="search-highlight">{{ author }}</em></h2> <h2>{% blocktranslate %}Extensions by{% endblocktranslate %} <em class="search-highlight">{{ author }}</em></h2>
{% endif %} {% endif %}
{% if team %} {% if team %}
<h2>{% blocktranslate %}Extensions by{% endblocktranslate %} <em class="search-highlight">{{ team.name }}</em></h2> <h2>{% blocktranslate %}Extensions by{% endblocktranslate %} <em class="search-highlight">{{ team.name }}</em></h2>
{% endif %} {% endif %}
{% if tag %}
<h2 class="d-flex align-items-center">
<span class="me-3">{% blocktranslate %}Extensions with the tag{% endblocktranslate %}</span>
{% include "extensions/components/badge_tag.html" %}
</h2>
{% endif %}
{% if type %}
<h2>{{ type }}</h2>
{% endif %}
{% if request.GET.q %} {% if request.GET.q %}
<h2>{{ page_obj.paginator.count }} result{{ page_obj.paginator.count | pluralize }} for <em class="search-highlight">{{ request.GET.q }}</em></h2> <h2>{{ page_obj.paginator.count }} result{{ page_obj.paginator.count | pluralize }} for <em class="search-highlight">{{ request.GET.q }}</em></h2>
{% endif %} {% endif %}
{% if tags %}
<div class="row">
<div class="col-md-12">
<div class="box p-2">
<div class="btn-row">
{% if tag %}
{# TODO @back-end: Find a proper way to get the plural tag type to build the URL. #}
<a class="btn btn-sm" href="/{{ tag.get_type_display|slugify }}s/" title="All">All</a>
{% else %}
<a class="btn btn-sm btn-primary" href="{% url 'extensions:by-type' type_slug=type|slugify %}" title="All">All</a>
{% endif %}
{% for list_tag in tags %}
<a class="align-items-center btn btn-sm d-flex {% if tag == list_tag %}btn-primary{% endif %}" href="{% url "extensions:by-tag" tag_slug=list_tag.slug %}" title="{{ list_tag.name }}">
<div>
{{ list_tag.name }}
</div>
{# TODO: @back-end add tags count dynamic #}
{% comment %}
<div class="align-items-center bg-primary d-flex h-3 fs-xs justify-content-center ms-2 rounded-circle w-3">
1
</div>
{% endcomment %}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{% if object_list %} {% if object_list %}
<div class="cards cards cards-lg-4 cards-md-3 cards-sm-2 mt-3"> <div class="cards cards cards-lg-4 cards-md-3 cards-sm-2">
{% for extension in object_list %} {% for extension in object_list %}
{% include "extensions/components/card.html" with show_type=False %} {% include "extensions/components/card.html" with show_type=False %}
{% endfor %} {% endfor %}

View File

@ -2,6 +2,10 @@
{# Upload new preview images #} {# Upload new preview images #}
{% load i18n %} {% load i18n %}
<div id="add-img-container" class="previews-list"> <div id="add-img-container" class="previews-list">
<div class="form-text">
Preview images are displayed in 16:9 ratio.
</div>
{{ add_preview_formset.management_form }} {{ add_preview_formset.management_form }}
{{ add_preview_formset.non_form_errors }} {{ add_preview_formset.non_form_errors }}
{% for newform in add_preview_formset %} {% for newform in add_preview_formset %}
@ -10,22 +14,23 @@
<div class="previews-list-item"> <div class="previews-list-item">
<div class="d-flex previews-list-item-thumbnail ps-3"> <div class="d-flex previews-list-item-thumbnail ps-3">
<div class="js-input-img-thumbnail previews-list-item-thumbnail-img" title="Preview"> <div class="js-input-img-thumbnail previews-list-item-thumbnail-img" title="Preview">
<div class="align-items-center d-flex js-input-img-thumbnail-icon justify-content-center"> <div class="ext-preview-thumbnail-icon js-input-img-thumbnail-icon">
<i class="i-image"></i> <i class="i-image"></i>/<i class="i-reel"></i>
</div> </div>
</div> </div>
</div> </div>
<div class="details flex-grow-1"> <div class="details">
<div class="js-input-img-caption-helper mb-2"> <div class="js-input-img-caption-helper">
{% include "common/components/field.html" with field=inlineform.caption label='Caption' placeholder="Describe the preview" %} {% include "common/components/field.html" with field=inlineform.caption label='Image or Video' placeholder="Description" %}
</div>
<div class="details-buttons js-input-img-helper">
<div>
{% trans "A JPEG, PNG or WebP image, or an MP4 video." as preview_help_text %}
{% include "common/components/field.html" with classes="form-control-sm" field=inlineform.source label='File' help_text=preview_help_text %}
</div>
<div class="btn-row">
<button class="btn btn-link btn-sm js-btn-reset-img-upload-form"><i class="i-refresh"></i> Reset</button>
</div> </div>
<div class="align-items-center d-flex js-input-img-helper justify-content-between">
{% include "common/components/field.html" with classes="form-control-sm" field=inlineform.source label='File' %}
<ul class="pt-0">
<li>
<button class="btn btn-link btn-sm js-btn-reset-img-upload-form ps-2 pe-0"><i class="i-refresh"></i> Reset</button>
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@ -38,15 +43,7 @@
<div class="col text-right mt-3"> <div class="col text-right mt-3">
<a id="btn-add-img" class="btn"> <a id="btn-add-img" class="btn">
<i class="i-plus"></i> <i class="i-plus"></i>
<span>{% trans 'Add Preview' %}</span> <span>{% trans 'Add Preview Slot' %}</span>
</a> </a>
</div> </div>
</div> </div>
<div class="row">
<div class="col">
<div class="form-text">
Preview images are displayed in 16:9 ratio.
</div>
</div>
</div>

View File

@ -15,18 +15,23 @@
<div class="js-preview-drag drag-widget is-draggable"> <div class="js-preview-drag drag-widget is-draggable">
<i class="i-menu"></i> <i class="i-menu"></i>
<div class="previews-list-item-thumbnail"> <div class="previews-list-item-thumbnail">
<div class="previews-list-item-thumbnail-img" style="background-image: url('{% if file.is_image %}{{ file.source.url }}{% elif file.is_video and file.thumbnail %}{{ file.thumbnail.url }}{% endif %}');" title="Preview"></div> {% with thumbnail_360p_url=file.thumbnail_360p_url file=file %}
<div
class="previews-list-item-thumbnail-img"
style="background-image: url('{% if file.is_image %}{{ thumbnail_360p_url }}{% elif file.is_video and file.thumbnail %}{{ file.thumbnail.url }}{% endif %}');" title="Preview">
</div>
{% endwith %}
</div> </div>
</div> </div>
<div class="details"> <div class="details">
<div> <div>
{% include "common/components/field.html" with field=inlineform.id %} {% include "common/components/field.html" with field=inlineform.id %}
{% include "common/components/field.html" with field=inlineform.caption %} {% include "common/components/field.html" with field=inlineform.caption label=file.get_type_display placeholder="Description" %}
{% include "common/components/field.html" with field=inlineform.position %} {% include "common/components/field.html" with field=inlineform.position %}
</div> </div>
<ul> <ul class="list-inline">
<li> <li>
<a href="{{ inlineform.instance.file.source.url }}" target="_blank"> <a href="{{ inlineform.instance.file.source.url }}" target="_blank" class="text-underline">
<small>Source File</small> <small>Source File</small>
</a> </a>
</li> </li>

View File

@ -2,7 +2,7 @@
{# Handles displaying and editing the featured image #} {# Handles displaying and editing the featured image #}
{% with inlineform=image_form|add_form_classes %} {% with inlineform=image_form|add_form_classes %}
{% with current_file=inlineform.instance.source %} {% with current_file=inlineform.instance.source %}
<div class="align-items-center d-flex justify-content-center mb-2 {{ image_form.prefix }}-preview" <div class="ext-preview-thumbnail-icon mb-2 {{ image_form.prefix }}-preview"
style="background-image: url('{% if current_file %}{{ current_file.url }}{% endif %}');" style="background-image: url('{% if current_file %}{{ current_file.url }}{% endif %}');"
title="{{ label }} of the extension"> title="{{ label }} of the extension">
<i class="i-image js-i-image"></i> <i class="i-image js-i-image"></i>

View File

@ -37,13 +37,13 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{% trans "Icon" as icon_label %} {% trans "Icon" as icon_label %}
{% trans "A 256 x 256 icon representing this extension." as icon_help_text %} {% trans "A 256 x 256 PNG icon representing this extension." as icon_help_text %}
{% include "extensions/manage/components/set_image.html" with image_form=icon_form label=icon_label help_text=icon_help_text %} {% include "extensions/manage/components/set_image.html" with image_form=icon_form label=icon_label help_text=icon_help_text %}
</div> </div>
<div class="col"> <div class="col">
{% trans "Featured image" as featured_image_label %} {% trans "Featured image" as featured_image_label %}
{% trans "Should have resolution of at least 1920 x 1080 and aspect ratio of 16:9." as featured_image_help_text %} {% trans "A JPEG, PNG or WebP image, at least 1920 x 1080 and with aspect ratio of 16:9." as featured_image_help_text %}
{% include "extensions/manage/components/set_image.html" with image_form=featured_image_form label=featured_image_label help_text=featured_image_help_text %} {% include "extensions/manage/components/set_image.html" with image_form=featured_image_form label=featured_image_label help_text=featured_image_help_text %}
</div> </div>
</div> </div>

View File

@ -46,7 +46,7 @@
{% switch is_alpha %} {% switch is_alpha %}
<a class="text-underline" href="https://docs.blender.org/manual/en/dev/extensions/#how-to-create-extensions">guidelines</a>. <a class="text-underline" href="https://docs.blender.org/manual/en/dev/extensions/#how-to-create-extensions">guidelines</a>.
{% else %} {% else %}
<a class="text-underline" href="https://docs.blender.org/manual/en/latest/extensions/#how-to-create-extensions">guidelines</a>. <a class="text-underline" href="https://docs.blender.org/manual/en/dev/extensions/#how-to-create-extensions">guidelines</a>.
{% endswitch %} {% endswitch %}
</strong> </strong>
</li> </li>
@ -60,12 +60,12 @@
{% with form=form|add_form_classes %} {% with form=form|add_form_classes %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{% include "common/components/field.html" with field=form.source label='File' %} {% include "common/components/field.html" with field=form.source label='File' classes="js-agree-with-terms-trigger js-submit-form-file-input" %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col mx-4 mt-4"> <div class="col mx-4 mt-4">
{% include "common/components/field.html" with field=form.agreed_with_terms classes="js-agree-with-terms-input" %} {% include "common/components/field.html" with field=form.agreed_with_terms classes="js-agree-with-terms-trigger js-agree-with-terms-checkbox" %}
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">

View File

@ -34,7 +34,7 @@ class DeleteTest(TestCase):
extension = version.extension extension = version.extension
version_file = version.file version_file = version.file
self.assertEqual(version_file.get_status_display(), 'Awaiting Review') self.assertEqual(version_file.get_status_display(), 'Awaiting Review')
self.assertEqual(extension.get_status_display(), 'Incomplete') self.assertEqual(extension.get_status_display(), 'Draft')
self.assertFalse(extension.is_listed) self.assertFalse(extension.is_listed)
self.assertEqual(extension.cannot_be_deleted_reasons, []) self.assertEqual(extension.cannot_be_deleted_reasons, [])
preview_file = extension.previews.first() preview_file = extension.previews.first()
@ -151,7 +151,7 @@ class DeleteTest(TestCase):
self.assertFalse(version.is_listed) self.assertFalse(version.is_listed)
extension = version.extension extension = version.extension
self.assertFalse(extension.is_listed) self.assertFalse(extension.is_listed)
self.assertEqual(extension.get_status_display(), 'Incomplete') self.assertEqual(extension.get_status_display(), 'Draft')
self.assertEqual(version.cannot_be_deleted_reasons, ['version_has_ratings']) self.assertEqual(version.cannot_be_deleted_reasons, ['version_has_ratings'])
self.assertEqual( self.assertEqual(

View File

@ -20,7 +20,7 @@ class ExtensionTest(TestCase):
extension__description='Extension description', extension__description='Extension description',
extension__website='https://example.com/', extension__website='https://example.com/',
extension__name='Extension name', extension__name='Extension name',
extension__status=Extension.STATUSES.INCOMPLETE, extension__status=Extension.STATUSES.DRAFT,
extension__support='https://example.com/', extension__support='https://example.com/',
file__metadata={ file__metadata={
'name': 'Extension name', 'name': 'Extension name',
@ -94,7 +94,7 @@ class VersionTest(TestCase):
extension__description='Extension description', extension__description='Extension description',
extension__website='https://example.com/', extension__website='https://example.com/',
extension__name='Extension name', extension__name='Extension name',
extension__status=Extension.STATUSES.INCOMPLETE, extension__status=Extension.STATUSES.DRAFT,
extension__support='https://example.com/', extension__support='https://example.com/',
) )
self.assertEqual(entries_for(self.version).count(), 0) self.assertEqual(entries_for(self.version).count(), 0)
@ -142,7 +142,7 @@ class UpdateMetadataTest(TestCase):
self.first_version = create_version( self.first_version = create_version(
extension__description='Extension description', extension__description='Extension description',
extension__name='name', extension__name='name',
extension__status=Extension.STATUSES.INCOMPLETE, extension__status=Extension.STATUSES.DRAFT,
extension__support='https://example.com/', extension__support='https://example.com/',
extension__website='https://example.com/', extension__website='https://example.com/',
file__metadata={ file__metadata={
@ -188,7 +188,7 @@ class UpdateMetadataTest(TestCase):
extension__description='Extension description', extension__description='Extension description',
extension__extension_id='lalalala', extension__extension_id='lalalala',
extension__name='name', extension__name='name',
extension__status=Extension.STATUSES.INCOMPLETE, extension__status=Extension.STATUSES.DRAFT,
extension__support='https://example.com/', extension__support='https://example.com/',
extension__website='https://example.com/', extension__website='https://example.com/',
file__metadata={ file__metadata={

View File

@ -571,7 +571,7 @@ class DraftsWarningTest(TestCase):
def test_page_contains_warning(self): def test_page_contains_warning(self):
version = create_version(extension__extension_id='draft_warning') version = create_version(extension__extension_id='draft_warning')
extension = version.extension extension = version.extension
self.assertEqual(extension.status, Extension.STATUSES.INCOMPLETE) self.assertEqual(extension.status, Extension.STATUSES.DRAFT)
self.client.force_login(extension.authors.all()[0]) self.client.force_login(extension.authors.all()[0])
response = self.client.get(reverse_lazy('extensions:submit')) response = self.client.get(reverse_lazy('extensions:submit'))
self.assertContains(response, extension.get_draft_url()) self.assertContains(response, extension.get_draft_url())

View File

@ -495,7 +495,7 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
) )
self.assertEqual(response2.status_code, 302) self.assertEqual(response2.status_code, 302)
extension.refresh_from_db() extension.refresh_from_db()
self.assertEqual(extension.status, extension.STATUSES.INCOMPLETE) self.assertEqual(extension.status, extension.STATUSES.DRAFT)
self.assertEqual( self.assertEqual(
extension.review_activity.last().type, ApprovalActivity.ActivityType.AWAITING_CHANGES extension.review_activity.last().type, ApprovalActivity.ActivityType.AWAITING_CHANGES
) )
@ -505,7 +505,7 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
def test_team_field_in_draft_form(self): def test_team_field_in_draft_form(self):
version = create_version( version = create_version(
extension__status=Extension.STATUSES.INCOMPLETE, extension__status=Extension.STATUSES.DRAFT,
) )
extension = version.extension extension = version.extension
author = extension.authors.first() author = extension.authors.first()

View File

@ -19,7 +19,7 @@ def _create_extension():
extension__description='**Description in bold**', extension__description='**Description in bold**',
extension__support='https://example.com/issues/', extension__support='https://example.com/issues/',
extension__website='https://example.com/', extension__website='https://example.com/',
extension__status=Extension.STATUSES.INCOMPLETE, extension__status=Extension.STATUSES.DRAFT,
extension__average_score=2.5, extension__average_score=2.5,
file__metadata={ file__metadata={
'name': 'Test Add-on', 'name': 'Test Add-on',

View File

@ -139,7 +139,7 @@ class UpdateExtensionView(
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
extension = self.extension extension = self.extension
if extension.status == extension.STATUSES.INCOMPLETE: if extension.status == extension.STATUSES.DRAFT:
return redirect('extensions:draft', slug=extension.slug, type_slug=extension.type_slug) return redirect('extensions:draft', slug=extension.slug, type_slug=extension.type_slug)
else: else:
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@ -348,12 +348,12 @@ class DraftExtensionView(
@property @property
def success_message(self) -> str: def success_message(self) -> str:
if self.extension.status == Extension.STATUSES.INCOMPLETE: if self.extension.status == Extension.STATUSES.DRAFT:
return "Updated successfully" return "Updated successfully"
return "Submitted to the Approval Queue" return "Submitted to the Approval Queue"
def test_func(self) -> bool: def test_func(self) -> bool:
return self.extension.status == Extension.STATUSES.INCOMPLETE return self.extension.status == Extension.STATUSES.DRAFT
def get_form_kwargs(self): def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs() form_kwargs = super().get_form_kwargs()

View File

@ -20,7 +20,7 @@ class UploadFileView(LoginRequiredMixin, CreateView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
drafts = ( drafts = (
Extension.objects.authored_by(self.request.user) Extension.objects.authored_by(self.request.user)
.filter(status=Extension.STATUSES.INCOMPLETE) .filter(status=Extension.STATUSES.DRAFT)
.distinct() .distinct()
) )
context['drafts'] = drafts context['drafts'] = drafts

View File

@ -220,7 +220,7 @@ class LicenseValidator(ListValidator):
error_message = mark_safe( error_message = mark_safe(
f'Manifest value error: <code>license</code> expects a list of ' f'Manifest value error: <code>license</code> expects a list of '
f'<a href="https://docs.blender.org/manual/en/dev/extensions/licenses.html">' f'<a href="https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html">'
f'supported licenses</a>. e.g., {cls.example}.' f'supported licenses</a>. e.g., {cls.example}.'
) )
if unknown_value: if unknown_value:
@ -253,7 +253,7 @@ class TagsValidatorBase:
error_message = mark_safe( error_message = mark_safe(
f'Manifest value error: <code>tags</code> expects a list of ' f'Manifest value error: <code>tags</code> expects a list of '
f'<a href="https://docs.blender.org/manual/en/dev/extensions/tags.html" ' f'<a href="https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html" '
f'target="_blank"> supported {type_name} tags</a>. e.g., {cls.example}. ' f'target="_blank"> supported {type_name} tags</a>. e.g., {cls.example}. '
) )
if unknown_value: if unknown_value:
@ -449,7 +449,7 @@ class SchemaVersionValidator(VersionValidator):
# TODO make a user manual page with the list of the different schemas. # TODO make a user manual page with the list of the different schemas.
return mark_safe( return mark_safe(
f'Manifest value error: <code>schema</code> version ({escape(value)}) ' f'Manifest value error: <code>schema</code> version ({escape(value)}) '
f'<a href="https://docs.blender.org/manual/en/dev/extensions/' f'<a href="https://docs.blender.org/manual/en/dev/advanced/extensions/'
f'getting_started.html#manifest" target="_blank">not supported</a>.' f'getting_started.html#manifest" target="_blank">not supported</a>.'
) )

View File

@ -2,7 +2,7 @@
- name: Coming up next - name: Coming up next
ansible.builtin.debug: ansible.builtin.debug:
msg: Running {{ playbook_type }} of {{ source_url }}@{{ branch }} to {{ env }} ({{ domain }}) msg: Running {{ playbook_type }} of {{ source_url }}@{{ branch }} to {{ env }} ({{ domain }})
when: 'playbook_type is defined' when: playbook_type is defined
tags: tags:
- always - always
@ -16,7 +16,7 @@
{% if not ansible_check_mode and not ansible_diff_mode %} and {% endif %}{% if not ansible_check_mode %}--check for dry run{% endif %}. {% if not ansible_check_mode and not ansible_diff_mode %} and {% endif %}{% if not ansible_check_mode %}--check for dry run{% endif %}.
{% endif %} {% endif %}
Press return to continue. Press Ctrl+c and then "a" to abort. Press return to continue. Press Ctrl+c and then "a" to abort.
when: 'playbook_type is defined' when: playbook_type is defined
tags: tags:
- always - always

View File

@ -1,10 +1,10 @@
--- ---
- name: "Writing {{ conf_d }}{{ conf_f }}" - name: Writing {{ conf_d }}{{ conf_f }}
vars: vars:
conf_d: /etc/nginx/conf.d/ conf_d: /etc/nginx/conf.d/
conf_f: log-format-upstreaminfo.conf conf_f: log-format-upstreaminfo.conf
ansible.builtin.template: ansible.builtin.template:
src: "templates/nginx/conf.d/{{ conf_f }}" src: templates/nginx/conf.d/{{ conf_f }}
dest: "{{ conf_d }}{{ conf_f }}" dest: "{{ conf_d }}{{ conf_f }}"
backup: true backup: true
mode: 0664 mode: 0664

View File

@ -24,6 +24,28 @@
tags: tags:
- deps - deps
- name: Configuring ClamAV
ansible.builtin.lineinfile:
path: /etc/clamav/clamd.conf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
backup: true
with_items:
- regexp: ^#*\s*MaxScanSize\s
line: MaxScanSize 200M
- regexp: ^#*\s*MaxFileSize\s
line: MaxFileSize 200M
- regexp: ^#*\s*PCREMaxFileSize\s
line: PCREMaxFileSize 200M
- regexp: ^#*\s*StreamMaxLength\s
line: StreamMaxLength 200M
notify:
- Restart ClamAV daemon
tags:
- deps
- clamav
- name: Creating user "{{ user }}:{{ group }}" - name: Creating user "{{ user }}:{{ group }}"
ansible.builtin.user: ansible.builtin.user:
name: "{{ user }}" name: "{{ user }}"
@ -60,3 +82,10 @@
- import_tasks: tasks/setup_other_services.yaml - import_tasks: tasks/setup_other_services.yaml
tags: tags:
- services - services
handlers:
- name: Restart ClamAV daemon
ansible.builtin.systemd:
name: clamav-daemon.service
state: restarted
enabled: true

View File

@ -5,7 +5,7 @@
owner: root owner: root
group: root group: root
state: directory state: directory
mode: '0755' mode: "0755"
tags: tags:
- uwsgi - uwsgi
- name: Copying uWSGI config files - name: Copying uWSGI config files

View File

@ -9,7 +9,7 @@ max_requests: 1000
max_requests_jitter: 50 max_requests_jitter: 50
port: 8200 port: 8200
workers: 2 workers: 2
client_max_body_size: "300m" client_max_body_size: "200m"
python_version: "3.10" python_version: "3.10"
delete_venv: false # set to true if venv has to be re-created from scratch delete_venv: false # set to true if venv has to be re-created from scratch

View File

@ -1,12 +1,20 @@
{% load filters %} {% load filters %}
<tr> <tr>
<td class="ext-review-list-type">{{ extension.get_type_display }}</td> <td class="ext-review-list-type">{{ extension.get_type_display }}</td>
<td> <td class="ext-review-list-name">
<a href="{{ extension.get_review_url }}"> <a href="{{ extension.get_review_url }}">
{% include "extensions/components/icon.html" %}
</a>
<a href="{{ extension.get_review_url }}" class="w-100">
{{ extension.name }} {{ extension.name }}
</a> </a>
</td> </td>
<td>{% include "extensions/components/authors.html" %}</td> <td>
{% include "extensions/components/authors.html" %}
{% if extension.team %}
<a class="text-secondary" href="{{ extension.team.get_absolute_url }}">({{ extension.team.name }})</a>
{% endif %}
</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">
<a href="{{ extension.get_review_url }}#activity-{{ stats.last_activity.id }}"> <a href="{{ extension.get_review_url }}#activity-{{ stats.last_activity.id }}">

View File

@ -40,26 +40,7 @@
<i class="i-chevron-down"></i> <i class="i-chevron-down"></i>
</button> </button>
<ul id="extension-admin-menu" class="dropdown-menu dropdown-menu-right js-dropdown-menu"> <ul id="extension-admin-menu" class="dropdown-menu dropdown-menu-right js-dropdown-menu">
<li> {% include "extensions/components/dropdown_admin.html" %}
<a href="{% url 'admin:extensions_extension_change' extension.pk %}" class="dropdown-item is-admin">
{% trans 'Extension' %}
</a>
</li>
{% if extension.latest_version %}
<li>
<a href="{% url 'admin:extensions_version_change' extension.latest_version.pk %}" class="dropdown-item is-admin">
{% trans 'Version' %}
</a>
</li>
{% endif %}
{% if extension.authors.all.0 %}
<li class="dropdown-divider"></li>
<li>
<a href="{% url 'admin:users_user_change' extension.authors.all.0.pk %}" class="dropdown-item is-admin">
{% trans 'User' %}
</a>
</li>
{% endif %}
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
@ -103,10 +84,16 @@
<li id="activity-{{ activity.id }}"> <li id="activity-{{ activity.id }}">
<article class="activity-item comment-card"> <article class="activity-item comment-card">
<i class="activity-icon {% if activity.type in status_change_types %}i-activity-{{ activity.get_type_display|slugify }}{% else %}i-comment{% endif %}"></i> <i class="activity-icon {% if activity.type in status_change_types %}i-activity-{{ activity.get_type_display|slugify }}{% else %}i-comment{% endif %}"></i>
<aside> <aside class="d-flex flex-column text-secondary">
<a href="{% url "extensions:by-author" user_id=activity.user.pk %}"> <a href="{% url "extensions:by-author" user_id=activity.user.pk %}">
{% include "users/components/profile_display.html" with user=activity.user classes="" %} {% include "users/components/profile_display.html" with user=activity.user classes="" %}
</a> </a>
{% if is_maintainer %}
<span title="Extension Maintainer"><i class="i-mic"></i></span>
{% elif activity.user.is_moderator %}
<span title="Moderator"><i class="i-shield"></i></span>
{% endif %}
</aside> </aside>
<div> <div>
<header> <header>
@ -123,7 +110,7 @@
{% endif %} {% endif %}
</li> </li>
<li class="ms-auto"> <li class="ms-auto">
<a href="#activity-{{ activity.id }}" title="{{ activity.date_created }}"> <a href="#activity-{{ activity.id }}" title="{{ activity.date_created|date:'l jS, F Y - H:i' }}">
{{ activity.date_created|naturaltime_compact }} {{ activity.date_created|naturaltime_compact }}
</a> </a>
</li> </li>

View File

@ -27,7 +27,7 @@
<tr> <tr>
<th>{% trans "Type" %}</th> <th>{% trans "Type" %}</th>
<th>{% trans "Name" %}</th> <th>{% trans "Name" %}</th>
<th>{% trans "Author" %}</th> <th>{% trans "Maintainer" %}</th>
<th>{% trans "Submitted" %}</th> <th>{% trans "Submitted" %}</th>
<th colspan="2">{% trans "Activity" %}</th> <th colspan="2">{% trans "Activity" %}</th>
<th></th> <th></th>

View File

@ -11,6 +11,7 @@ class TeamsUsersInline(admin.TabularInline):
@admin.register(teams.models.Team) @admin.register(teams.models.Team)
class TeamAdmin(admin.ModelAdmin): class TeamAdmin(admin.ModelAdmin):
save_on_top = True
list_display = ('name', 'slug', 'user_count', 'date_created') list_display = ('name', 'slug', 'user_count', 'date_created')
list_display_links = ['name'] list_display_links = ['name']
list_filter = ['date_created'] list_filter = ['date_created']