Tags: Make tags dependent on type, and remove taggit #43
|
@ -1,13 +1,13 @@
|
|||
import random
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from taggit.models import Tag
|
||||
|
||||
from common.tests.factories.extensions import create_approved_version, create_version
|
||||
from common.tests.factories.files import FileFactory
|
||||
from common.tests.factories.teams import TeamFactory
|
||||
from files.models import File
|
||||
from constants.version_permissions import VERSION_PERMISSION_FILE, VERSION_PERMISSION_NETWORK
|
||||
from extensions.models import Extension, Tag
|
||||
|
||||
FILE_SOURCES = {
|
||||
"blender-kitsu": {
|
||||
|
@ -59,14 +59,18 @@ class Command(BaseCommand):
|
|||
help = 'Generate fake data with extensions, users and versions using test factories.'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
tags = list(Tag.objects.values_list('name', flat=True))
|
||||
tags = {
|
||||
type_id: list(Tag.objects.filter(type=type_id).values_list('name', flat=True))
|
||||
for type_id, _ in Extension.TYPES
|
||||
}
|
||||
|
||||
# Create a fixed example
|
||||
example_version = create_approved_version(
|
||||
file__status=File.STATUSES.APPROVED,
|
||||
# extension__status=Extension.STATUSES.APPROVED,
|
||||
extension__name='Blender Kitsu',
|
||||
extension__extension_id='blender_kitsu',
|
||||
tags=['Generic'],
|
||||
tags=['Development'],
|
||||
extension__description=EXAMPLE_DESCRIPTION,
|
||||
extension__support='https://developer.blender.org/',
|
||||
extension__website='https://studio.blender.org/',
|
||||
|
@ -93,11 +97,12 @@ class Command(BaseCommand):
|
|||
|
||||
# Create a few publicly listed extensions
|
||||
for i in range(10):
|
||||
extension__type = random.choice(Extension.TYPES)[0]
|
||||
create_approved_version(
|
||||
file__status=File.STATUSES.APPROVED,
|
||||
# extension__status=Extension.STATUSES.APPROVED,
|
||||
extension__type=random.choice((File.TYPES.BPY, File.TYPES.THEME)),
|
||||
tags=random.sample(tags, k=1),
|
||||
extension__type=extension__type,
|
||||
tags=random.sample(tags[extension__type], k=1),
|
||||
extension__previews=[
|
||||
FileFactory(
|
||||
type=File.TYPES.IMAGE,
|
||||
|
@ -112,11 +117,12 @@ class Command(BaseCommand):
|
|||
|
||||
# Create a few unlisted extension versions
|
||||
for i in range(5):
|
||||
extension__type = random.choice(Extension.TYPES)[0]
|
||||
create_version(
|
||||
file__status=random.choice(
|
||||
(File.STATUSES.DISABLED, File.STATUSES.DISABLED_BY_AUTHOR)
|
||||
),
|
||||
tags=random.sample(tags, k=1),
|
||||
tags=random.sample(tags[extension__type], k=1),
|
||||
)
|
||||
|
||||
example_version.extension.average_score = 5.0
|
||||
|
|
|
@ -10,12 +10,12 @@ from django.template.defaultfilters import stringfilter
|
|||
from django.utils.safestring import mark_safe
|
||||
|
||||
from markupsafe import Markup
|
||||
from taggit.models import Tag
|
||||
|
||||
from common.markdown import (
|
||||
render as render_markdown,
|
||||
render_as_text as render_markdown_as_text,
|
||||
)
|
||||
from extensions.models import Tag
|
||||
|
||||
import utils
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from mdgen import MarkdownPostProvider
|
|||
import factory
|
||||
import factory.fuzzy
|
||||
|
||||
from extensions.models import Extension, Version
|
||||
from extensions.models import Extension, Version, Tag
|
||||
from ratings.models import Rating
|
||||
|
||||
fake_markdown = Faker()
|
||||
|
@ -77,8 +77,11 @@ class VersionFactory(DjangoModelFactory):
|
|||
if not create:
|
||||
return
|
||||
|
||||
if extracted:
|
||||
self.tags.add(*extracted)
|
||||
if not extracted:
|
||||
return
|
||||
|
||||
tags = Tag.objects.filter(name__in=extracted)
|
||||
self.tags.add(*tags)
|
||||
|
||||
|
||||
def create_version(**kwargs) -> 'Version':
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
[
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "3D View",
|
||||
"slug": "3d-view"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Add Mesh",
|
||||
"slug": "add-mesh"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Add Curve",
|
||||
"slug": "add-curve"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "Animation",
|
||||
"slug": "animation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Compositing",
|
||||
"slug": "compositing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "Development",
|
||||
"slug": "development"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "Game Engine",
|
||||
"slug": "game-engine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"name": "Import-Export",
|
||||
"slug": "import-export"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"name": "Lighting",
|
||||
"slug": "lighting"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"name": "Material",
|
||||
"slug": "material"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"name": "Mesh",
|
||||
"slug": "mesh"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"name": "Node",
|
||||
"slug": "node"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"name": "Object",
|
||||
"slug": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"name": "Paint",
|
||||
"slug": "paint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"name": "Physics",
|
||||
"slug": "physics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"name": "Render",
|
||||
"slug": "render"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"name": "Rigging",
|
||||
"slug": "rigging"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"name": "Scene",
|
||||
"slug": "scene"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 19,
|
||||
"fields": {
|
||||
"name": "Sequencer",
|
||||
"slug": "sequencer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 20,
|
||||
"fields": {
|
||||
"name": "System",
|
||||
"slug": "system"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 21,
|
||||
"fields": {
|
||||
"name": "Text Editor",
|
||||
"slug": "text-editor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 22,
|
||||
"fields": {
|
||||
"name": "UV",
|
||||
"slug": "uv"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 23,
|
||||
"fields": {
|
||||
"name": "User Interface",
|
||||
"slug": "user-interface"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 24,
|
||||
"fields": {
|
||||
"name": "Modeling",
|
||||
"slug": "modeling"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "taggit.tag",
|
||||
"pk": 25,
|
||||
"fields": {
|
||||
"name": "Pipeline",
|
||||
"slug": "pipeline"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,125 @@
|
|||
# Generated by Django 4.0.6 on 2024-02-27 17:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
from taggit.models import TaggedItem
|
||||
import logging
|
||||
|
||||
from constants.base import EXTENSION_TYPE_CHOICES
|
||||
from utils import slugify
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
themes_tags = (
|
||||
'Dark',
|
||||
'Light',
|
||||
'Print',
|
||||
'Accessibility',
|
||||
'High Contrast',
|
||||
)
|
||||
|
||||
addons_tags = (
|
||||
'3D View',
|
||||
'Add Mesh',
|
||||
'Add Curve',
|
||||
'Animation',
|
||||
'Compositing',
|
||||
'Development',
|
||||
'Game Engine',
|
||||
'Import-Export',
|
||||
'Lighting',
|
||||
'Material',
|
||||
'Modeling',
|
||||
'Mesh',
|
||||
'Node',
|
||||
'Object',
|
||||
'Paint',
|
||||
'Pipeline',
|
||||
'Physics',
|
||||
'Render',
|
||||
'Rigging',
|
||||
'Scene',
|
||||
'Sequencer',
|
||||
'System',
|
||||
'Text Editor',
|
||||
'UV',
|
||||
'User Interface',
|
||||
)
|
||||
|
||||
|
||||
def populate_tags_database(apps, schema_editor):
|
||||
Tag = apps.get_model("extensions", "Tag")
|
||||
for tag_name in themes_tags:
|
||||
tag = Tag(
|
||||
name=tag_name,
|
||||
slug=slugify(tag_name),
|
||||
type=EXTENSION_TYPE_CHOICES.THEME,
|
||||
)
|
||||
tag.save()
|
||||
|
||||
for tag_name in addons_tags:
|
||||
tag = Tag(
|
||||
name=tag_name,
|
||||
slug=slugify(tag_name),
|
||||
type=EXTENSION_TYPE_CHOICES.BPY,
|
||||
)
|
||||
tag.save()
|
||||
|
||||
|
||||
def migrate_tags(apps, schema_editor):
|
||||
model = apps.get_model('extensions', 'version')
|
||||
Tag = apps.get_model("extensions", "Tag")
|
||||
TagsOld = apps.get_model('taggit', 'Tag')
|
||||
|
||||
all_tags = TagsOld.objects.all()
|
||||
for tag_old in all_tags:
|
||||
for item in tag_old.taggit_taggeditem_items.all():
|
||||
version = model.objects.filter(id=item.object_id).first()
|
||||
tag = Tag.objects.filter(name=tag_old.name)
|
||||
if not tag:
|
||||
logger.warning(f'Tag not supported anymore: {tag_old.name}')
|
||||
continue
|
||||
version.tags.add(tag.first())
|
||||
version.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extensions', '0023_apply_new_licenses'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='version',
|
||||
old_name='tags',
|
||||
new_name='tags_old',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_modified', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=128)),
|
||||
('slug', models.SlugField(blank=False, null=False)),
|
||||
('type', models.PositiveSmallIntegerField(choices=[(1, 'Add-on'), (2, 'Theme')], editable=False, null=False, blank=False)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'unique_together': {('name', 'type')}
|
||||
},
|
||||
),
|
||||
migrations.RunPython(populate_tags_database),
|
||||
migrations.AddField(
|
||||
model_name='version',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, related_name='versions', to='extensions.tag'),
|
||||
),
|
||||
migrations.RunPython(migrate_tags),
|
||||
migrations.RemoveField(
|
||||
model_name='version',
|
||||
name='tags_old',
|
||||
),
|
||||
]
|
|
@ -8,8 +8,6 @@ from django.core.exceptions import ObjectDoesNotExist, BadRequest, ValidationErr
|
|||
from django.db import models, transaction
|
||||
from django.db.models import F, Q, Count
|
||||
from django.urls import reverse
|
||||
from taggit.managers import TaggableManager
|
||||
from taggit.models import Tag
|
||||
|
||||
from common.fields import FilterableManyToManyField
|
||||
from common.model_mixins import CreatedModifiedMixin, TrackChangesMixin, SoftDeleteMixin
|
||||
|
@ -398,6 +396,20 @@ class VersionPermission(CreatedModifiedMixin, models.Model):
|
|||
return cls.objects.filter(slug__startswith=slug).first()
|
||||
|
||||
|
||||
class Tag(CreatedModifiedMixin, models.Model):
|
||||
TYPES = EXTENSION_TYPE_CHOICES
|
||||
|
||||
name = models.CharField(max_length=128, null=False, blank=False)
|
||||
slug = models.SlugField(blank=False, null=False)
|
||||
type = models.PositiveSmallIntegerField(choices=TYPES, editable=False, null=False, blank=False)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('name', 'type')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class VersionManager(models.Manager):
|
||||
@property
|
||||
def exclude_deleted(self):
|
||||
|
@ -476,7 +488,7 @@ class Version(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, SoftDeleteMi
|
|||
)
|
||||
licenses = models.ManyToManyField(License, related_name='versions', blank=False)
|
||||
|
||||
tags = TaggableManager()
|
||||
tags = models.ManyToManyField(Tag, related_name='versions', blank=True)
|
||||
|
||||
schema_version = extensions.fields.VersionStringField(
|
||||
max_length=64,
|
||||
|
@ -542,8 +554,9 @@ class Version(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, SoftDeleteMi
|
|||
return
|
||||
|
||||
for tag_name in _tags:
|
||||
tag = Tag.objects.filter(name__startswith=tag_name).first()
|
||||
if not tag:
|
||||
try:
|
||||
tag = Tag.objects.filter(name=tag_name).first()
|
||||
except ObjectDoesNotExist:
|
||||
error_message = f'Unsupported tag in manifest file: {tag_name}'
|
||||
log.error(error_message)
|
||||
raise BadRequest(error_message)
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
{% if tags %}
|
||||
<div class="col-md-2">
|
||||
<aside class="is-sticky pt-3">
|
||||
<div class="list-filters">
|
||||
<h3>Tags</h3>
|
||||
<ul>
|
||||
{% for list_tag in all_tags %}
|
||||
{% if list_tag.taggit_taggeditem_items.all|length %}
|
||||
{% for list_tag in tags %}
|
||||
{% if list_tag.versions.all|length %}
|
||||
<li class="{% if tag == list_tag %}is-active{% endif %}">
|
||||
<a href="{% url "extensions:by-tag" tag_slug=list_tag.slug %}" title="{{ list_tag.name }}">
|
||||
{{ list_tag.name }}
|
||||
|
@ -23,8 +24,9 @@
|
|||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-10 my-4">
|
||||
<div class="col-md-{% if tags %}10{% else %}12{% endif %} my-4">
|
||||
{% if author %}
|
||||
<h2>{% blocktranslate %}Extensions by{% endblocktranslate %} <em class="search-highlight">{{ author }}</em></h2>
|
||||
{% endif %}
|
||||
|
|
|
@ -4,7 +4,7 @@ from common.tests.factories.extensions import create_version
|
|||
|
||||
|
||||
class ApproveExtensionTest(TestCase):
|
||||
fixtures = ['tags', 'licenses']
|
||||
fixtures = ['licenses']
|
||||
|
||||
def test_approve_extension(self): # TODO
|
||||
create_version().extension
|
||||
|
|
|
@ -10,7 +10,7 @@ from constants.licenses import LICENSE_GPL3
|
|||
from constants.base import EXTENSION_TYPE_CHOICES, EXTENSION_TYPE_SLUGS_SINGULAR
|
||||
from extensions.models import Extension, Version, VersionPermission
|
||||
from files.models import File
|
||||
from files.validators import ManifestValidator
|
||||
from files.validators import ManifestValidator, TagsAddonsValidator, TagsThemesValidator
|
||||
|
||||
import tempfile
|
||||
import shutil
|
||||
|
@ -236,7 +236,7 @@ class ValidateManifestTest(CreateFileTest):
|
|||
|
||||
|
||||
class ValidateManifestFields(TestCase):
|
||||
fixtures = ['licenses', 'version_permissions', 'tags']
|
||||
fixtures = ['licenses', 'version_permissions']
|
||||
|
||||
def setUp(self):
|
||||
self.mandatory_fields = {
|
||||
|
@ -369,11 +369,12 @@ class ValidateManifestFields(TestCase):
|
|||
ManifestValidator(data)
|
||||
self.assertEqual(1, len(e.exception.messages))
|
||||
|
||||
def test_tags(self):
|
||||
def test_tags_addons(self):
|
||||
data = {
|
||||
**self.mandatory_fields,
|
||||
**self.optional_fields,
|
||||
}
|
||||
data['type'] = 'add-on'
|
||||
|
||||
data['tags'] = ['Render']
|
||||
ManifestValidator(data)
|
||||
|
@ -382,9 +383,36 @@ class ValidateManifestFields(TestCase):
|
|||
with self.assertRaises(ValidationError) as e:
|
||||
ManifestValidator(data)
|
||||
|
||||
message_begin = "Manifest value error: tags expects a list of supported tags, e.g.: ['Animation', 'Sequencer']. Visit"
|
||||
message_begin = "Manifest value error: tags expects a list of supported add-on tags, e.g.: ['Animation', 'Sequencer']. Visit"
|
||||
self.assertIn(message_begin, e.exception.messages[0])
|
||||
|
||||
data['tags'] = ['Dark']
|
||||
with self.assertRaises(ValidationError) as e:
|
||||
ManifestValidator(data)
|
||||
self.assertEqual(1, len(e.exception.messages))
|
||||
|
||||
def test_tags_themes(self):
|
||||
data = {
|
||||
**self.mandatory_fields,
|
||||
**self.optional_fields,
|
||||
}
|
||||
data['type'] = 'theme'
|
||||
|
||||
data['tags'] = ['Light']
|
||||
ManifestValidator(data)
|
||||
|
||||
data['tags'] = ['UnsupportedTag']
|
||||
with self.assertRaises(ValidationError) as e:
|
||||
ManifestValidator(data)
|
||||
|
||||
message_begin = "Manifest value error: tags expects a list of supported theme tags, e.g.: ['Dark', 'Accessibility']. Visit"
|
||||
self.assertIn(message_begin, e.exception.messages[0])
|
||||
|
||||
data['tags'] = ['Render']
|
||||
with self.assertRaises(ValidationError) as e:
|
||||
ManifestValidator(data)
|
||||
self.assertEqual(1, len(e.exception.messages))
|
||||
|
||||
def test_permissions(self):
|
||||
data = {
|
||||
**self.mandatory_fields,
|
||||
|
@ -457,9 +485,11 @@ class ValidateManifestFields(TestCase):
|
|||
|
||||
# Good cops.
|
||||
data['type'] = EXTENSION_TYPE_SLUGS_SINGULAR[EXTENSION_TYPE_CHOICES.BPY]
|
||||
data['tags'] = TagsAddonsValidator.example
|
||||
ManifestValidator(data)
|
||||
|
||||
data['type'] = EXTENSION_TYPE_SLUGS_SINGULAR[EXTENSION_TYPE_CHOICES.THEME]
|
||||
data['tags'] = TagsThemesValidator.example
|
||||
ManifestValidator(data)
|
||||
|
||||
# Bad cops.
|
||||
|
|
|
@ -11,7 +11,7 @@ from extensions.models import Extension
|
|||
|
||||
class ExtensionTest(TestCase):
|
||||
maxDiff = None
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -79,7 +79,7 @@ class ExtensionTest(TestCase):
|
|||
|
||||
class VersionTest(TestCase):
|
||||
maxDiff = None
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
|
|
@ -63,7 +63,7 @@ EXPECTED_EXTENSION_DATA = {
|
|||
|
||||
class SubmitFileTest(TestCase):
|
||||
maxDiff = None
|
||||
fixtures = ['tags', 'licenses']
|
||||
fixtures = ['licenses']
|
||||
url = reverse_lazy('extensions:submit')
|
||||
|
||||
def _test_submit_addon(
|
||||
|
@ -211,7 +211,7 @@ for file_name, data in EXPECTED_EXTENSION_DATA.items():
|
|||
|
||||
class SubmitFinaliseTest(TestCase):
|
||||
maxDiff = None
|
||||
fixtures = ['tags', 'licenses']
|
||||
fixtures = ['licenses']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -322,7 +322,7 @@ class SubmitFinaliseTest(TestCase):
|
|||
|
||||
|
||||
class NewVersionTest(TestCase):
|
||||
fixtures = ['tags', 'licenses']
|
||||
fixtures = ['licenses']
|
||||
|
||||
def setUp(self):
|
||||
self.version = create_version(extension__extension_id='amaranth')
|
||||
|
|
|
@ -34,7 +34,7 @@ POST_DATA = {
|
|||
|
||||
|
||||
class UpdateTest(TestCase):
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def test_get_manage_page(self):
|
||||
extension = create_approved_version().extension
|
||||
|
|
|
@ -21,7 +21,7 @@ def _create_extension():
|
|||
|
||||
|
||||
class _BaseTestCase(TestCase):
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def _check_detail_page(self, response, extension):
|
||||
pass
|
||||
|
|
|
@ -6,9 +6,7 @@ from django.shortcuts import get_object_or_404, redirect
|
|||
from django.views.generic.list import ListView
|
||||
|
||||
|
||||
from taggit.models import Tag
|
||||
|
||||
from extensions.models import Extension, Version
|
||||
from extensions.models import Extension, Version, Tag
|
||||
from constants.base import (
|
||||
EXTENSION_TYPE_SLUGS,
|
||||
EXTENSION_TYPE_PLURAL,
|
||||
|
@ -98,7 +96,6 @@ class SearchView(ListedExtensionsView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['all_tags'] = Tag.objects.all()
|
||||
|
||||
if self.kwargs.get('user_id'):
|
||||
context['author'] = get_object_or_404(User, pk=self.kwargs['user_id'])
|
||||
|
@ -108,4 +105,14 @@ class SearchView(ListedExtensionsView):
|
|||
context['type'] = self._get_type_by_slug()
|
||||
if self.kwargs.get('team_slug'):
|
||||
context['team'] = get_object_or_404(teams.models.Team, slug=self.kwargs['team_slug'])
|
||||
|
||||
# Determine which tags to list depending on the context.
|
||||
if context.get('type'):
|
||||
tag_type_id = self._get_type_id_by_slug()
|
||||
context['tags'] = Tag.objects.filter(type=tag_type_id)
|
||||
elif context.get('tag'):
|
||||
tag_type_id = context['tag'].type
|
||||
context['tags'] = Tag.objects.filter(type=tag_type_id)
|
||||
else:
|
||||
pass
|
||||
return context
|
||||
|
|
|
@ -8,8 +8,6 @@ from django.contrib.auth import get_user_model
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
# from taggit.models import Tag
|
||||
|
||||
from common.model_mixins import CreatedModifiedMixin, TrackChangesMixin, SoftDeleteMixin
|
||||
from files.utils import get_sha256
|
||||
from constants.base import (
|
||||
|
|
|
@ -13,7 +13,7 @@ User = get_user_model()
|
|||
|
||||
class FileTest(TestCase):
|
||||
maxDiff = None
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
|
|
@ -5,10 +5,9 @@ import logging
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import FileExtensionValidator, validate_unicode_slug
|
||||
from taggit.models import Tag
|
||||
|
||||
from extensions.models import Extension, License, VersionPermission
|
||||
from constants.base import EXTENSION_TYPE_SLUGS_SINGULAR
|
||||
from extensions.models import Extension, License, VersionPermission, Tag
|
||||
from constants.base import EXTENSION_TYPE_SLUGS_SINGULAR, EXTENSION_TYPE_CHOICES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -176,40 +175,65 @@ class LicenseValidator(ListValidator):
|
|||
return error_message
|
||||
|
||||
|
||||
class TagsValidator:
|
||||
example = ['Animation', 'Sequencer']
|
||||
|
||||
class TagsValidatorBase:
|
||||
@classmethod
|
||||
def validate(cls, *, name: str, value: list[str], manifest: dict) -> str:
|
||||
"""Return error message if there is no tag, or if tag is not a valid one"""
|
||||
is_error = False
|
||||
type_name = EXTENSION_TYPE_SLUGS_SINGULAR[cls.type]
|
||||
|
||||
if type(value) != list:
|
||||
is_error = True
|
||||
else:
|
||||
for tag in value:
|
||||
if Tag.objects.filter(name=tag):
|
||||
if Tag.objects.filter(name=tag, type=cls.type):
|
||||
continue
|
||||
is_error = True
|
||||
logger.info(f'Tag unavailable: {tag}')
|
||||
|
||||
for tag in value:
|
||||
if Tag.objects.filter(name=tag):
|
||||
continue
|
||||
is_error = True
|
||||
break
|
||||
type_slug = manifest.get('type')
|
||||
logger.info(f'Tag unavailable for {type_slug}: {tag}')
|
||||
|
||||
if not is_error:
|
||||
return
|
||||
|
||||
error_message = (
|
||||
f'Manifest value error: {name} expects a list of supported tags, e.g.: {cls.example}. '
|
||||
f'Manifest value error: {name} expects a list of supported {type_name} '
|
||||
f'tags, e.g.: {cls.example}. '
|
||||
'Visit https://docs.blender.org/manual/en/dev/extensions/tags.html to learn more.'
|
||||
)
|
||||
|
||||
return error_message
|
||||
|
||||
|
||||
class TagsAddonsValidator(TagsValidatorBase):
|
||||
example = ['Animation', 'Sequencer']
|
||||
type = EXTENSION_TYPE_CHOICES.BPY
|
||||
|
||||
|
||||
class TagsThemesValidator(TagsValidatorBase):
|
||||
example = ['Dark', 'Accessibility']
|
||||
type = EXTENSION_TYPE_CHOICES.THEME
|
||||
|
||||
|
||||
class TagsValidator:
|
||||
example = ['User Interface']
|
||||
|
||||
@classmethod
|
||||
def validate(cls, *, name: str, value: list[str], manifest: dict) -> str:
|
||||
"""Return error message if there is no tag, or if tag is not a valid one"""
|
||||
tags_lookup = {
|
||||
EXTENSION_TYPE_SLUGS_SINGULAR[EXTENSION_TYPE_CHOICES.BPY]: TagsAddonsValidator,
|
||||
EXTENSION_TYPE_SLUGS_SINGULAR[EXTENSION_TYPE_CHOICES.THEME]: TagsThemesValidator,
|
||||
}
|
||||
|
||||
type_slug = manifest.get('type')
|
||||
meta_class = tags_lookup.get(type_slug)
|
||||
|
||||
if meta_class is None:
|
||||
return
|
||||
|
||||
return meta_class.validate(name=name, value=value, manifest=manifest)
|
||||
|
||||
|
||||
class TypeValidator:
|
||||
example = 'add-on'
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from ratings.models import Rating
|
|||
|
||||
|
||||
class RatingsViewTest(TestCase):
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def test_get_anonymous(self):
|
||||
version = create_approved_version(ratings=[])
|
||||
|
@ -76,7 +76,7 @@ class RatingsViewTest(TestCase):
|
|||
|
||||
|
||||
class AddRatingViewTest(TestCase):
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def test_get_anonymous_redirects_to_login(self):
|
||||
version = create_approved_version(ratings=[])
|
||||
|
|
|
@ -10,7 +10,7 @@ from stats.models import ExtensionView, ExtensionDownload, ExtensionCountedStat
|
|||
|
||||
|
||||
class WriteStatsCommandTest(TestCase):
|
||||
fixtures = ['dev', 'tags', 'licenses']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def test_command_updates_extensions_view_counters(self):
|
||||
out = StringIO()
|
||||
|
|
|
@ -12,7 +12,7 @@ User = get_user_model()
|
|||
|
||||
|
||||
class TestTasks(TestCase):
|
||||
fixtures = ['dev', 'licenses', 'tags']
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
def test_handle_deletion_request_anonymized(self):
|
||||
now = timezone.now()
|
||||
|
|
Loading…
Reference in New Issue