Refactor Extension and Version: explicit constructors from File #191
@ -2,18 +2,16 @@ import logging
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
import faker
|
from django.db import transaction
|
||||||
|
|
||||||
from common.tests.factories.extensions import create_approved_version, create_version
|
from common.tests.factories.extensions import create_approved_version, create_version
|
||||||
from common.tests.factories.files import FileFactory
|
from common.tests.factories.files import FileFactory, ImageFactory
|
||||||
from common.tests.factories.teams import TeamFactory
|
from common.tests.factories.teams import TeamFactory
|
||||||
from files.models import File
|
from files.models import File
|
||||||
from constants.version_permissions import VERSION_PERMISSION_FILE, VERSION_PERMISSION_NETWORK
|
from constants.version_permissions import VERSION_PERMISSION_FILE, VERSION_PERMISSION_NETWORK
|
||||||
from constants.licenses import LICENSE_GPL2, LICENSE_GPL3
|
from constants.licenses import LICENSE_GPL2, LICENSE_GPL3
|
||||||
from extensions.models import Extension, Tag
|
from extensions.models import Extension, Tag
|
||||||
|
|
||||||
_faker = faker.Faker()
|
|
||||||
|
|
||||||
FILE_SOURCES = {
|
FILE_SOURCES = {
|
||||||
"blender-kitsu": {
|
"blender-kitsu": {
|
||||||
"file": 'files/ed/ed656b177b01999e6fcd0e37c34ced471ef88c89db578f337e40958553dca5d2.zip',
|
"file": 'files/ed/ed656b177b01999e6fcd0e37c34ced471ef88c89db578f337e40958553dca5d2.zip',
|
||||||
@ -64,6 +62,7 @@ LICENSES = (LICENSE_GPL2.id, LICENSE_GPL3.id)
|
|||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Generate fake data with extensions, users and versions using test factories.'
|
help = 'Generate fake data with extensions, users and versions using test factories.'
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
verbosity = int(options['verbosity'])
|
verbosity = int(options['verbosity'])
|
||||||
root_logger = logging.getLogger('root')
|
root_logger = logging.getLogger('root')
|
||||||
@ -81,70 +80,54 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Create a fixed example
|
# Create a fixed example
|
||||||
example_version = create_approved_version(
|
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=['Development'],
|
|
||||||
extension__description=EXAMPLE_DESCRIPTION,
|
|
||||||
extension__support='https://developer.blender.org/',
|
|
||||||
extension__website='https://studio.blender.org/',
|
|
||||||
blender_version_min='2.93.0',
|
|
||||||
version='0.1.5-alpha+f52258de',
|
|
||||||
file=FileFactory(
|
file=FileFactory(
|
||||||
type=File.TYPES.BPY,
|
metadata__blender_version_min='2.93.0',
|
||||||
source=FILE_SOURCES["blender-kitsu"]["file"],
|
metadata__id='blender_kitsu',
|
||||||
|
metadata__name='Blender Kitsu',
|
||||||
|
metadata__support='https://developer.blender.org/',
|
||||||
|
metadata__tags=['Development'],
|
||||||
|
metadata__version='0.1.5-alpha+f52258de',
|
||||||
|
metadata__website='https://studio.blender.org/',
|
||||||
original_hash=FILE_SOURCES["blender-kitsu"]["hash"],
|
original_hash=FILE_SOURCES["blender-kitsu"]["hash"],
|
||||||
size_bytes=FILE_SOURCES["blender-kitsu"]["size"],
|
size_bytes=FILE_SOURCES["blender-kitsu"]["size"],
|
||||||
|
source=FILE_SOURCES["blender-kitsu"]["file"],
|
||||||
status=File.STATUSES.APPROVED,
|
status=File.STATUSES.APPROVED,
|
||||||
metadata={'name': 'Blender Kitsu'},
|
|
||||||
),
|
),
|
||||||
extension__previews=[
|
|
||||||
FileFactory(
|
|
||||||
type=File.TYPES.IMAGE,
|
|
||||||
source=source,
|
|
||||||
status=File.STATUSES.APPROVED,
|
|
||||||
)
|
|
||||||
for source in PREVIEW_SOURCES[-2:]
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
for preview in [
|
||||||
|
ImageFactory(source=source, status=File.STATUSES.APPROVED)
|
||||||
|
for source in PREVIEW_SOURCES[-2:]
|
||||||
|
]:
|
||||||
|
example_version.extension.previews.add(preview)
|
||||||
example_version.permissions.add(VERSION_PERMISSION_FILE.id)
|
example_version.permissions.add(VERSION_PERMISSION_FILE.id)
|
||||||
example_version.permissions.add(VERSION_PERMISSION_NETWORK.id)
|
example_version.permissions.add(VERSION_PERMISSION_NETWORK.id)
|
||||||
example_version.licenses.add(LICENSE_GPL2.id)
|
example_version.licenses.add(LICENSE_GPL2.id)
|
||||||
|
|
||||||
# Create a few publicly listed extensions
|
# Create a few publicly listed extensions
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
extension__type = random.choice(Extension.TYPES)[0]
|
type = random.choice(Extension.TYPES)[0]
|
||||||
name = _faker.catch_phrase()
|
|
||||||
version = create_approved_version(
|
version = create_approved_version(
|
||||||
file__status=File.STATUSES.APPROVED,
|
metadata__tags=random.sample(tags[type], k=1),
|
||||||
file__metadata={'name': name},
|
status=File.STATUSES.APPROVED,
|
||||||
# extension__status=Extension.STATUSES.APPROVED,
|
type=type,
|
||||||
extension__name=name,
|
|
||||||
extension__type=extension__type,
|
|
||||||
tags=random.sample(tags[extension__type], k=1),
|
|
||||||
extension__previews=[
|
|
||||||
FileFactory(
|
|
||||||
type=File.TYPES.IMAGE,
|
|
||||||
source=source,
|
|
||||||
status=File.STATUSES.APPROVED,
|
|
||||||
)
|
|
||||||
for source in random.sample(
|
|
||||||
PREVIEW_SOURCES, k=random.randint(1, len(PREVIEW_SOURCES) - 1)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
for preview in [
|
||||||
|
ImageFactory(source=source, status=File.STATUSES.APPROVED)
|
||||||
|
for source in random.sample(
|
||||||
|
PREVIEW_SOURCES, k=random.randint(1, len(PREVIEW_SOURCES) - 1)
|
||||||
|
)
|
||||||
|
]:
|
||||||
|
version.extension.previews.add(preview)
|
||||||
for i in range(random.randint(1, len(LICENSES))):
|
for i in range(random.randint(1, len(LICENSES))):
|
||||||
version.licenses.add(LICENSES[i])
|
version.licenses.add(LICENSES[i])
|
||||||
|
|
||||||
# Create a few unlisted extension versions
|
# Create a few unlisted extension versions
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
extension__type = random.choice(Extension.TYPES)[0]
|
type = random.choice(Extension.TYPES)[0]
|
||||||
version = create_version(
|
version = create_version(
|
||||||
file__status=random.choice(
|
metadata__tags=random.sample(tags[type], k=1),
|
||||||
(File.STATUSES.DISABLED, File.STATUSES.DISABLED_BY_AUTHOR)
|
status=random.choice((File.STATUSES.DISABLED, File.STATUSES.DISABLED_BY_AUTHOR)),
|
||||||
),
|
type=type,
|
||||||
tags=random.sample(tags[extension__type], k=1),
|
|
||||||
)
|
)
|
||||||
for i in range(random.randint(1, len(LICENSES))):
|
for i in range(random.randint(1, len(LICENSES))):
|
||||||
version.licenses.add(LICENSES[i])
|
version.licenses.add(LICENSES[i])
|
||||||
|
@ -7,11 +7,11 @@ from django.db.models import signals
|
|||||||
import factory
|
import factory
|
||||||
|
|
||||||
from common.tests.factories.extensions import create_version, RatingFactory
|
from common.tests.factories.extensions import create_version, RatingFactory
|
||||||
from common.tests.factories.files import FileFactory
|
from common.tests.factories.files import ImageFactory
|
||||||
from files.models import File
|
from files.models import File
|
||||||
from constants.licenses import LICENSE_GPL2, LICENSE_GPL3
|
from constants.licenses import LICENSE_GPL2, LICENSE_GPL3
|
||||||
from extensions.models import Extension, Tag
|
from extensions.models import Extension, Tag
|
||||||
from utils import slugify, chunked
|
from utils import chunked
|
||||||
import faker
|
import faker
|
||||||
|
|
||||||
_faker = faker.Faker()
|
_faker = faker.Faker()
|
||||||
@ -109,31 +109,25 @@ class Command(BaseCommand):
|
|||||||
for _ in i_chunked:
|
for _ in i_chunked:
|
||||||
name = _faker.catch_phrase() + _faker.bothify('###???')
|
name = _faker.catch_phrase() + _faker.bothify('###???')
|
||||||
extension_id = name.replace(' ', '_')
|
extension_id = name.replace(' ', '_')
|
||||||
slug = slugify(extension_id)[:50]
|
|
||||||
version = create_version(
|
version = create_version(
|
||||||
file__status=file_status,
|
metadata__id=extension_id,
|
||||||
file__metadata={'name': name, 'id': extension_id},
|
metadata__name=name,
|
||||||
tags=random.sample(tags[_type], k=1),
|
metadata__tags=random.sample(tags[_type], k=1),
|
||||||
extension__extension_id=extension_id,
|
status=file_status,
|
||||||
extension__is_listed=extension_status == e_sts.APPROVED,
|
type=_type,
|
||||||
extension__name=name,
|
|
||||||
extension__slug=slug,
|
|
||||||
extension__status=extension_status,
|
|
||||||
extension__type=_type,
|
|
||||||
extension__previews=[
|
|
||||||
FileFactory(
|
|
||||||
type=File.TYPES.IMAGE,
|
|
||||||
source=source,
|
|
||||||
status=file_status,
|
|
||||||
)
|
|
||||||
for source in random.sample(
|
|
||||||
PREVIEW_SOURCES,
|
|
||||||
k=random.randint(1, len(PREVIEW_SOURCES) - 1),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
# Create these separately
|
|
||||||
ratings=[],
|
|
||||||
)
|
)
|
||||||
|
version.extension.is_listed = extension_status == e_sts.APPROVED
|
||||||
|
version.extension.status = extension_status
|
||||||
|
version.extension.save(update_fields={'is_listed', 'status'})
|
||||||
|
for preview in [
|
||||||
|
ImageFactory(source=source, status=file_status)
|
||||||
|
for source in random.sample(
|
||||||
|
PREVIEW_SOURCES,
|
||||||
|
k=random.randint(1, len(PREVIEW_SOURCES) - 1),
|
||||||
|
)
|
||||||
|
]:
|
||||||
|
version.extension.previews.add(preview)
|
||||||
|
|
||||||
for i in range(random.randint(1, len(LICENSES))):
|
for i in range(random.randint(1, len(LICENSES))):
|
||||||
version.licenses.add(LICENSES[i])
|
version.licenses.add(LICENSES[i])
|
||||||
if version.is_listed:
|
if version.is_listed:
|
||||||
|
@ -6,7 +6,8 @@ from mdgen import MarkdownPostProvider
|
|||||||
import factory
|
import factory
|
||||||
import factory.fuzzy
|
import factory.fuzzy
|
||||||
|
|
||||||
from extensions.models import Extension, Version, Tag, Preview, Platform
|
from common.tests.factories.files import FileFactory
|
||||||
|
from extensions.models import Extension, Version, Preview
|
||||||
from ratings.models import Rating
|
from ratings.models import Rating
|
||||||
|
|
||||||
fake_markdown = Faker()
|
fake_markdown = Faker()
|
||||||
@ -56,73 +57,17 @@ class RatingFactory(DjangoModelFactory):
|
|||||||
extension = factory.LazyAttribute(lambda o: o.version.extension)
|
extension = factory.LazyAttribute(lambda o: o.version.extension)
|
||||||
|
|
||||||
|
|
||||||
class VersionFactory(DjangoModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = Version
|
|
||||||
|
|
||||||
extension = factory.SubFactory(ExtensionFactory)
|
|
||||||
version = factory.LazyAttribute(
|
|
||||||
lambda _: f'{random.randint(0, 9)}.{random.randint(0, 9)}.{random.randint(0, 9)}'
|
|
||||||
)
|
|
||||||
blender_version_min = factory.fuzzy.FuzzyChoice(
|
|
||||||
{'2.83.1', '2.93.0', '2.93.8', '3.0.0', '3.2.1'}
|
|
||||||
)
|
|
||||||
download_count = factory.Faker('random_int')
|
|
||||||
tagline = factory.Faker('bs')
|
|
||||||
|
|
||||||
file = factory.SubFactory(
|
|
||||||
'common.tests.factories.files.FileFactory',
|
|
||||||
metadata=factory.Dict(
|
|
||||||
{
|
|
||||||
'name': factory.Faker('name'),
|
|
||||||
'support': factory.Faker('url'),
|
|
||||||
'website': factory.Faker('url'),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
ratings = factory.RelatedFactoryList(
|
|
||||||
RatingFactory, size=lambda: random.randint(0, 5), factory_related_name='version'
|
|
||||||
)
|
|
||||||
|
|
||||||
@factory.post_generation
|
|
||||||
def files(self, create, extracted, **kwargs):
|
|
||||||
if not create:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not extracted:
|
|
||||||
self.files.add(self.file)
|
|
||||||
return
|
|
||||||
|
|
||||||
for file in extracted:
|
|
||||||
self.files.add(file)
|
|
||||||
|
|
||||||
@factory.post_generation
|
|
||||||
def platforms(self, create, extracted, **kwargs):
|
|
||||||
if not create:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not extracted:
|
|
||||||
return
|
|
||||||
|
|
||||||
platforms = Platform.objects.filter(slug__in=extracted)
|
|
||||||
self.platforms.add(*platforms)
|
|
||||||
|
|
||||||
@factory.post_generation
|
|
||||||
def tags(self, create, extracted, **kwargs):
|
|
||||||
if not create:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not extracted:
|
|
||||||
return
|
|
||||||
|
|
||||||
tags = Tag.objects.filter(name__in=extracted)
|
|
||||||
self.tags.add(*tags)
|
|
||||||
|
|
||||||
|
|
||||||
def create_version(**kwargs) -> 'Version':
|
def create_version(**kwargs) -> 'Version':
|
||||||
version = VersionFactory(**kwargs)
|
extension = kwargs.pop('extension', None)
|
||||||
version.extension.authors.add(version.file.user)
|
file = kwargs.pop('file', None)
|
||||||
return version
|
|
||||||
|
if not file:
|
||||||
|
file = FileFactory(**kwargs)
|
||||||
|
|
||||||
|
if not extension:
|
||||||
|
extension = Extension.create_from_file(file)
|
||||||
|
|
||||||
|
return extension.create_version_from_file(file)
|
||||||
|
|
||||||
|
|
||||||
def create_approved_version(**kwargs) -> 'Version':
|
def create_approved_version(**kwargs) -> 'Version':
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
from factory.django import DjangoModelFactory
|
from factory.django import DjangoModelFactory
|
||||||
import factory
|
import factory
|
||||||
import factory.fuzzy
|
import factory.fuzzy
|
||||||
@ -5,23 +7,48 @@ import factory.fuzzy
|
|||||||
from files.models import File
|
from files.models import File
|
||||||
|
|
||||||
|
|
||||||
|
class ManifestFactory(factory.DictFactory):
|
||||||
|
name = factory.Faker('name')
|
||||||
|
id = factory.Faker('slug')
|
||||||
|
support = factory.Faker('url')
|
||||||
|
website = factory.Faker('url')
|
||||||
|
version = factory.LazyAttribute(
|
||||||
|
lambda _: f'{random.randint(0, 9)}.{random.randint(0, 9)}.{random.randint(0, 9)}',
|
||||||
|
)
|
||||||
|
blender_version_min = factory.fuzzy.FuzzyChoice(
|
||||||
|
{'2.83.1', '2.93.0', '2.93.8', '3.0.0', '3.2.1'}
|
||||||
|
)
|
||||||
|
tagline = factory.Faker('bs')
|
||||||
|
schema_version = '1.0.0'
|
||||||
|
platforms = []
|
||||||
|
tags = []
|
||||||
|
|
||||||
|
|
||||||
class FileFactory(DjangoModelFactory):
|
class FileFactory(DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = File
|
model = File
|
||||||
|
|
||||||
original_name = factory.LazyAttribute(lambda x: x.source)
|
|
||||||
original_hash = factory.Faker('lexify', text='fakehash:??????????????????', letters='deadbeef')
|
|
||||||
hash = factory.Faker('lexify', text='fakehash:??????????????????', letters='deadbeef')
|
hash = factory.Faker('lexify', text='fakehash:??????????????????', letters='deadbeef')
|
||||||
|
metadata = factory.SubFactory(ManifestFactory)
|
||||||
|
original_hash = factory.Faker('lexify', text='fakehash:??????????????????', letters='deadbeef')
|
||||||
|
original_name = factory.LazyAttribute(lambda x: x.source)
|
||||||
size_bytes = factory.Faker('random_int', min=1234)
|
size_bytes = factory.Faker('random_int', min=1234)
|
||||||
source = factory.Faker('file_name', extension='zip')
|
source = factory.Faker('file_name', extension='zip')
|
||||||
|
type = File.TYPES.BPY
|
||||||
user = factory.SubFactory('common.tests.factories.users.UserFactory')
|
user = factory.SubFactory('common.tests.factories.users.UserFactory')
|
||||||
|
|
||||||
metadata = factory.Dict({})
|
|
||||||
|
|
||||||
|
|
||||||
class ImageFactory(FileFactory):
|
class ImageFactory(FileFactory):
|
||||||
|
metadata = {}
|
||||||
original_name = factory.Faker('file_name', extension='png')
|
original_name = factory.Faker('file_name', extension='png')
|
||||||
|
size_bytes = 1234
|
||||||
source = 'images/de/deadbeef.png'
|
source = 'images/de/deadbeef.png'
|
||||||
type = File.TYPES.IMAGE
|
type = File.TYPES.IMAGE
|
||||||
size_bytes = 1234
|
|
||||||
|
|
||||||
|
class VideoFactory(FileFactory):
|
||||||
|
metadata = {}
|
||||||
|
original_name = factory.Faker('file_name', extension='mp4')
|
||||||
|
size_bytes = 12345678
|
||||||
|
source = 'images/be/beefcafe.mp4'
|
||||||
|
type = File.TYPES.VIDEO
|
||||||
|
@ -58,6 +58,7 @@ def construct_fake_notifications() -> list['NotificationFactory']:
|
|||||||
),
|
),
|
||||||
Verb.DISMISSED_ABUSE_REPORT: None,
|
Verb.DISMISSED_ABUSE_REPORT: None,
|
||||||
Verb.RATED_EXTENSION: RatingFactory.build(
|
Verb.RATED_EXTENSION: RatingFactory.build(
|
||||||
|
extension=fake_extension,
|
||||||
text=fake.paragraph(nb_sentences=2),
|
text=fake.paragraph(nb_sentences=2),
|
||||||
),
|
),
|
||||||
Verb.REPORTED_EXTENSION: None, # TODO: fake action_object
|
Verb.REPORTED_EXTENSION: None, # TODO: fake action_object
|
||||||
|
@ -84,6 +84,7 @@ class ExtensionAdmin(admin.ModelAdmin):
|
|||||||
'icon',
|
'icon',
|
||||||
'featured_image',
|
'featured_image',
|
||||||
'latest_version',
|
'latest_version',
|
||||||
|
'is_listed',
|
||||||
)
|
)
|
||||||
autocomplete_fields = ('team',)
|
autocomplete_fields = ('team',)
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ class ExtensionAdmin(admin.ModelAdmin):
|
|||||||
('icon', 'featured_image'),
|
('icon', 'featured_image'),
|
||||||
'status',
|
'status',
|
||||||
'latest_version',
|
'latest_version',
|
||||||
|
'is_listed',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -185,6 +185,55 @@ class Extension(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|||||||
]
|
]
|
||||||
ordering = ['-average_score', '-date_created', 'name']
|
ordering = ['-average_score', '-date_created', 'name']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_file(cls, file: File) -> 'Extension':
|
||||||
|
extension = cls(**file.parsed_extension_fields)
|
||||||
|
with transaction.atomic():
|
||||||
|
extension.save()
|
||||||
|
extension.authors.add(file.user)
|
||||||
|
return extension
|
||||||
|
|
||||||
|
def create_version_from_file(self, file: File, release_notes='') -> 'Version':
|
||||||
|
"""This should be the only method to create a new Version object.
|
||||||
|
|
||||||
|
Side effects:
|
||||||
|
- updates file status and adds an approval queue comment when extension is listed
|
||||||
|
- updates extension.latest_version
|
||||||
|
"""
|
||||||
|
fields = file.parsed_version_fields
|
||||||
|
licenses = fields.pop('licenses', [])
|
||||||
|
permissions = fields.pop('permissions', {})
|
||||||
|
if permissions:
|
||||||
|
permissions = list(permissions.keys())
|
||||||
|
platforms = fields.pop('platforms', [])
|
||||||
|
tags = fields.pop('tags', [])
|
||||||
|
version = Version(**fields, extension=self, release_notes=release_notes, file=file)
|
||||||
|
with transaction.atomic():
|
||||||
|
version.save()
|
||||||
|
version.files.add(file)
|
||||||
|
version.set_initial_licenses(licenses)
|
||||||
|
version.set_initial_permissions(permissions)
|
||||||
|
version.set_initial_platforms(platforms)
|
||||||
|
version.set_initial_tags(tags)
|
||||||
|
|
||||||
|
# auto approving our file if extension is already listed (i.e. have been approved)
|
||||||
|
if self.is_listed:
|
||||||
|
args = {'f_id': file.pk, 'pk': version.pk, 's': file.source.name}
|
||||||
|
log.info('Auto-approving file pk=%(f_id)s of Version pk=%(pk)s source=%(s)s', args)
|
||||||
|
file.status = File.STATUSES.APPROVED
|
||||||
|
file.save(update_fields={'status', 'date_modified'})
|
||||||
|
|
||||||
|
ApprovalActivity(
|
||||||
|
type=ApprovalActivity.ActivityType.UPLOADED_NEW_VERSION,
|
||||||
|
user=file.user,
|
||||||
|
extension=self,
|
||||||
|
message=f'uploaded new version: {version}',
|
||||||
|
).save()
|
||||||
|
|
||||||
|
self.update_latest_version()
|
||||||
|
|
||||||
|
return version
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.get_type_display()} "{self.name}"'
|
return f'{self.get_type_display()} "{self.name}"'
|
||||||
|
|
||||||
@ -486,28 +535,6 @@ class VersionManager(models.Manager):
|
|||||||
def unlisted(self):
|
def unlisted(self):
|
||||||
return self.exclude(file__status=FILE_STATUS_CHOICES.APPROVED)
|
return self.exclude(file__status=FILE_STATUS_CHOICES.APPROVED)
|
||||||
|
|
||||||
def update_or_create(self, *args, **kwargs):
|
|
||||||
# Stash the ManyToMany to be created after the Version has a valid ID already
|
|
||||||
licenses = kwargs.pop('licenses', [])
|
|
||||||
permissions = kwargs.pop('permissions', {})
|
|
||||||
# FIXME: ideally this dict->list transformation should be a caller's responsibility
|
|
||||||
# but we plan to change the model soon, so getting a dict as an input here makes sense
|
|
||||||
if permissions:
|
|
||||||
permissions = list(permissions.keys())
|
|
||||||
platforms = kwargs.pop('platforms', [])
|
|
||||||
tags = kwargs.pop('tags', [])
|
|
||||||
file = kwargs.get('file') # FIXME legacy_version: replace get with pop
|
|
||||||
|
|
||||||
version, result = super().update_or_create(*args, **kwargs)
|
|
||||||
|
|
||||||
# Add the ManyToMany to the already initialized Version
|
|
||||||
version.files.add(file)
|
|
||||||
version.set_initial_licenses(licenses)
|
|
||||||
version.set_initial_permissions(permissions)
|
|
||||||
version.set_initial_platforms(platforms)
|
|
||||||
version.set_initial_tags(tags)
|
|
||||||
return version, result
|
|
||||||
|
|
||||||
|
|
||||||
class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
||||||
track_changes_to_fields = {
|
track_changes_to_fields = {
|
||||||
@ -725,32 +752,12 @@ class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
is_new = self.pk is None
|
|
||||||
update_fields = kwargs.get('update_fields', None)
|
update_fields = kwargs.get('update_fields', None)
|
||||||
was_changed, old_state = self.pre_save_record(update_fields=update_fields)
|
was_changed, old_state = self.pre_save_record(update_fields=update_fields)
|
||||||
self.record_status_change(was_changed, old_state, **kwargs)
|
self.record_status_change(was_changed, old_state, **kwargs)
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
if not is_new:
|
|
||||||
return
|
|
||||||
|
|
||||||
# auto approving our file if extension is already listed (i.e. have been approved)
|
|
||||||
if self.extension.is_listed:
|
|
||||||
args = {'f_id': self.file.pk, 'pk': self.pk, 's': self.file.source.name}
|
|
||||||
log.info('Auto-approving file pk=%(f_id)s of Version pk=%(pk)s source=%(s)s', args)
|
|
||||||
self.file.status = File.STATUSES.APPROVED
|
|
||||||
self.file.save(update_fields={'status', 'date_modified'})
|
|
||||||
|
|
||||||
ApprovalActivity(
|
|
||||||
type=ApprovalActivity.ActivityType.UPLOADED_NEW_VERSION,
|
|
||||||
user=self.file.user,
|
|
||||||
extension=self.extension,
|
|
||||||
message=f'uploaded new version: {self.version}',
|
|
||||||
).save()
|
|
||||||
|
|
||||||
self.extension.update_latest_version()
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
if self == self.extension.latest_version:
|
if self == self.extension.latest_version:
|
||||||
|
@ -79,7 +79,7 @@ class ResponseFormatTest(APITestCase):
|
|||||||
self.assertEqual(v['archive_url'][-4:], '.zip')
|
self.assertEqual(v['archive_url'][-4:], '.zip')
|
||||||
|
|
||||||
def test_maintaner_is_team(self):
|
def test_maintaner_is_team(self):
|
||||||
version = create_approved_version(blender_version_min='4.0.1')
|
version = create_approved_version(metadata__blender_version_min='4.0.1')
|
||||||
team = Team(name='test team', slug='test-team')
|
team = Team(name='test team', slug='test-team')
|
||||||
team.save()
|
team.save()
|
||||||
version.extension.team = team
|
version.extension.team = team
|
||||||
@ -107,9 +107,9 @@ class ResponseFormatTest(APITestCase):
|
|||||||
|
|
||||||
class FiltersTest(APITestCase):
|
class FiltersTest(APITestCase):
|
||||||
def test_blender_version_filter(self):
|
def test_blender_version_filter(self):
|
||||||
create_approved_version(blender_version_min='4.0.1')
|
create_approved_version(metadata__blender_version_min='4.0.1')
|
||||||
create_approved_version(blender_version_min='4.1.1')
|
create_approved_version(metadata__blender_version_min='4.1.1')
|
||||||
create_approved_version(blender_version_min='4.2.1')
|
create_approved_version(metadata__blender_version_min='4.2.1')
|
||||||
url = reverse('extensions:api')
|
url = reverse('extensions:api')
|
||||||
|
|
||||||
json = self.client.get(
|
json = self.client.get(
|
||||||
@ -131,8 +131,8 @@ class FiltersTest(APITestCase):
|
|||||||
self.assertEqual(len(json3['data']), 3)
|
self.assertEqual(len(json3['data']), 3)
|
||||||
|
|
||||||
def test_platform_filter(self):
|
def test_platform_filter(self):
|
||||||
create_approved_version(platforms=['windows-x64'])
|
create_approved_version(metadata__platforms=['windows-x64'])
|
||||||
create_approved_version(platforms=['windows-arm64'])
|
create_approved_version(metadata__platforms=['windows-arm64'])
|
||||||
create_approved_version()
|
create_approved_version()
|
||||||
url = reverse('extensions:api')
|
url = reverse('extensions:api')
|
||||||
|
|
||||||
@ -151,27 +151,30 @@ class FiltersTest(APITestCase):
|
|||||||
self.assertEqual(len(json['data']), 1)
|
self.assertEqual(len(json['data']), 1)
|
||||||
|
|
||||||
def test_blender_version_filter_latest_not_max_version(self):
|
def test_blender_version_filter_latest_not_max_version(self):
|
||||||
version = create_approved_version(blender_version_min='4.0.1')
|
version = create_approved_version(metadata__blender_version_min='4.0.1')
|
||||||
version.date_created
|
date_created = version.date_created
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
create_approved_version(
|
version = create_approved_version(
|
||||||
blender_version_min='4.2.1',
|
|
||||||
extension=extension,
|
extension=extension,
|
||||||
date_created=version.date_created + timedelta(days=1),
|
metadata__blender_version_min='4.2.1',
|
||||||
version='2.0.0',
|
metadata__version='2.0.0',
|
||||||
)
|
)
|
||||||
|
version.date_created = date_created + timedelta(days=1)
|
||||||
|
version.save(update_fields={'date_created'})
|
||||||
create_approved_version(
|
create_approved_version(
|
||||||
blender_version_min='3.0.0',
|
|
||||||
extension=extension,
|
extension=extension,
|
||||||
date_created=version.date_created + timedelta(days=2),
|
metadata__blender_version_min='3.0.0',
|
||||||
version='1.0.1',
|
metadata__version='1.0.1',
|
||||||
)
|
)
|
||||||
|
version.date_created = date_created + timedelta(days=2)
|
||||||
|
version.save(update_fields={'date_created'})
|
||||||
create_approved_version(
|
create_approved_version(
|
||||||
blender_version_min='4.2.1',
|
|
||||||
extension=extension,
|
extension=extension,
|
||||||
date_created=version.date_created + timedelta(days=3),
|
metadata__blender_version_min='4.2.1',
|
||||||
version='2.0.1',
|
metadata__version='2.0.1',
|
||||||
)
|
)
|
||||||
|
version.date_created = date_created + timedelta(days=3)
|
||||||
|
version.save(update_fields={'date_created'})
|
||||||
url = reverse('extensions:api')
|
url = reverse('extensions:api')
|
||||||
|
|
||||||
json = self.client.get(
|
json = self.client.get(
|
||||||
@ -190,9 +193,9 @@ class VersionUploadAPITest(APITestCase):
|
|||||||
|
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.version = create_approved_version(
|
self.version = create_approved_version(
|
||||||
extension__extension_id="amaranth",
|
metadata__id="amaranth",
|
||||||
version="1.0.7",
|
metadata__version="1.0.7",
|
||||||
file__user=self.user,
|
user=self.user,
|
||||||
)
|
)
|
||||||
self.extension = self.version.extension
|
self.extension = self.version.extension
|
||||||
self.file_path = TEST_FILES_DIR / "amaranth-1.0.8.zip"
|
self.file_path = TEST_FILES_DIR / "amaranth-1.0.8.zip"
|
||||||
@ -218,7 +221,7 @@ class VersionUploadAPITest(APITestCase):
|
|||||||
def test_version_upload_extension_not_maintained_by_user(self):
|
def test_version_upload_extension_not_maintained_by_user(self):
|
||||||
other_user = UserFactory()
|
other_user = UserFactory()
|
||||||
other_extension = create_approved_version(
|
other_extension = create_approved_version(
|
||||||
extension__extension_id='other_extension', file__user=other_user
|
metadata__id='other_extension', user=other_user
|
||||||
).extension
|
).extension
|
||||||
|
|
||||||
with open(self.file_path, 'rb') as version_file:
|
with open(self.file_path, 'rb') as version_file:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import factory
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.contrib.admin.models import LogEntry, DELETION
|
from django.contrib.admin.models import LogEntry, DELETION
|
||||||
@ -27,25 +26,23 @@ class DeleteTest(TestCase):
|
|||||||
original_name='extension_feature_image.png',
|
original_name='extension_feature_image.png',
|
||||||
source='images/b0/b03fa981527593fbe15b28cf37c020220c3d83021999eab036b87f3bca9c9168.png',
|
source='images/b0/b03fa981527593fbe15b28cf37c020220c3d83021999eab036b87f3bca9c9168.png',
|
||||||
)
|
)
|
||||||
version = create_version(
|
version = create_version(status=files.models.File.STATUSES.AWAITING_REVIEW)
|
||||||
file__status=files.models.File.STATUSES.AWAITING_REVIEW,
|
|
||||||
ratings=[],
|
|
||||||
extension__icon=FileFactory(
|
|
||||||
type=files.models.File.TYPES.IMAGE,
|
|
||||||
original_name='extension_icon_final.png',
|
|
||||||
source='images/8a/8a01102de8573d50bbc90033f55f232b7cacc4f1eb3e3c3d851615841d2956e1.png',
|
|
||||||
),
|
|
||||||
extension__featured_image=reused_image,
|
|
||||||
extension__previews=[
|
|
||||||
reused_image,
|
|
||||||
FileFactory(
|
|
||||||
type=files.models.File.TYPES.IMAGE,
|
|
||||||
original_name='extension_preview_001.png',
|
|
||||||
source='images/b0/b03fa981527593fbe15b28cf37c020220c3d83021999eab036b87f3bca9c9168.png',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
|
extension.featured_image = reused_image
|
||||||
|
extension.icon = FileFactory(
|
||||||
|
type=files.models.File.TYPES.IMAGE,
|
||||||
|
original_name='extension_icon_final.png',
|
||||||
|
source='images/8a/8a01102de8573d50bbc90033f55f232b7cacc4f1eb3e3c3d851615841d2956e1.png',
|
||||||
|
)
|
||||||
|
extension.save(update_fields={'featured_image', 'icon'})
|
||||||
|
extension.previews.add(reused_image)
|
||||||
|
extension.previews.add(
|
||||||
|
FileFactory(
|
||||||
|
type=files.models.File.TYPES.IMAGE,
|
||||||
|
original_name='extension_preview_001.png',
|
||||||
|
source='images/b0/b03fa981527593fbe15b28cf37c020220c3d83021999eab036b87f3bca9c9168.png',
|
||||||
|
)
|
||||||
|
)
|
||||||
version_file = version.file
|
version_file = version.file
|
||||||
icon = extension.icon
|
icon = extension.icon
|
||||||
featured_image = extension.featured_image
|
featured_image = extension.featured_image
|
||||||
@ -152,7 +149,7 @@ class DeleteTest(TestCase):
|
|||||||
# TODO: check that files were deleted from storage (create a temp one prior to the check)
|
# TODO: check that files were deleted from storage (create a temp one prior to the check)
|
||||||
|
|
||||||
def test_publicly_listed_extension_cannot_be_deleted(self):
|
def test_publicly_listed_extension_cannot_be_deleted(self):
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
self.assertTrue(version.is_listed)
|
self.assertTrue(version.is_listed)
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
self.assertTrue(extension.is_listed)
|
self.assertTrue(extension.is_listed)
|
||||||
@ -169,12 +166,8 @@ class DeleteTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_rated_extension_cannot_be_deleted(self):
|
def test_rated_extension_cannot_be_deleted(self):
|
||||||
version = create_version(
|
version = create_version(status=files.models.File.STATUSES.AWAITING_REVIEW)
|
||||||
file__status=files.models.File.STATUSES.AWAITING_REVIEW,
|
RatingFactory(version=version)
|
||||||
ratings=factory.RelatedFactoryList(
|
|
||||||
RatingFactory, size=1, factory_related_name='version'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
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)
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
import factory
|
|
||||||
|
|
||||||
from common.tests.factories.extensions import create_approved_version
|
from common.tests.factories.extensions import create_approved_version
|
||||||
from common.tests.factories.files import FileFactory
|
|
||||||
from common.tests.factories.users import UserFactory
|
from common.tests.factories.users import UserFactory
|
||||||
|
|
||||||
from constants.licenses import LICENSE_GPL3
|
from constants.licenses import LICENSE_GPL3
|
||||||
@ -56,20 +54,10 @@ class CreateFileTest(TestCase):
|
|||||||
|
|
||||||
def _create_valid_extension(self, extension_id):
|
def _create_valid_extension(self, extension_id):
|
||||||
return create_approved_version(
|
return create_approved_version(
|
||||||
extension__name='Blender Kitsu',
|
metadata__id=extension_id,
|
||||||
extension__extension_id=extension_id,
|
metadata__name='Blender Kitsu',
|
||||||
version='0.1.5-alpha+f52258de',
|
metadata__version='0.1.5-alpha+f52258de',
|
||||||
file=FileFactory(
|
status=File.STATUSES.APPROVED,
|
||||||
type=File.TYPES.BPY,
|
|
||||||
status=File.STATUSES.APPROVED,
|
|
||||||
metadata=factory.Dict(
|
|
||||||
{
|
|
||||||
'name': 'Blender Kitsu',
|
|
||||||
'support': factory.Faker('url'),
|
|
||||||
'website': factory.Faker('url'),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_file_from_data(self, filename, file_data, user):
|
def _create_file_from_data(self, filename, file_data, user):
|
||||||
@ -258,20 +246,10 @@ class ValidateManifestTest(CreateFileTest):
|
|||||||
|
|
||||||
# Create another unrelated extension with the same target version we will use.
|
# Create another unrelated extension with the same target version we will use.
|
||||||
create_approved_version(
|
create_approved_version(
|
||||||
extension__name='Another Extension',
|
metadata__id='another_extension',
|
||||||
extension__extension_id='another_extension',
|
metadata__name='Another Extension',
|
||||||
version='0.1.6',
|
metadata__version='0.1.6',
|
||||||
file=FileFactory(
|
status=File.STATUSES.APPROVED,
|
||||||
type=File.TYPES.BPY,
|
|
||||||
status=File.STATUSES.APPROVED,
|
|
||||||
metadata=factory.Dict(
|
|
||||||
{
|
|
||||||
'name': factory.Faker('name'),
|
|
||||||
'support': factory.Faker('url'),
|
|
||||||
'website': factory.Faker('url'),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# The same author is to send a new version to thte same extension
|
# The same author is to send a new version to thte same extension
|
||||||
@ -713,9 +691,6 @@ class VersionPermissionsTest(CreateFileTest):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
# Although this creates the file, the version is not created until
|
|
||||||
# we click on Submit for Review.
|
|
||||||
|
|
||||||
# Check step 2: finalise new version and send to review
|
# Check step 2: finalise new version and send to review
|
||||||
url = response['Location']
|
url = response['Location']
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
@ -6,7 +6,6 @@ from common.admin import get_admin_change_path
|
|||||||
from common.log_entries import entries_for
|
from common.log_entries import entries_for
|
||||||
from common.tests.factories.extensions import create_version
|
from common.tests.factories.extensions import create_version
|
||||||
from common.tests.factories.users import UserFactory
|
from common.tests.factories.users import UserFactory
|
||||||
from extensions.models import Extension
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionTest(TestCase):
|
class ExtensionTest(TestCase):
|
||||||
@ -16,17 +15,9 @@ class ExtensionTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.extension = create_version(
|
self.extension = create_version(
|
||||||
file__size_bytes=123,
|
metadata__name='Extension name',
|
||||||
extension__description='Extension description',
|
metadata__support='https://example.com/',
|
||||||
extension__website='https://example.com/',
|
metadata__website='https://example.com/',
|
||||||
extension__name='Extension name',
|
|
||||||
extension__status=Extension.STATUSES.DRAFT,
|
|
||||||
extension__support='https://example.com/',
|
|
||||||
file__metadata={
|
|
||||||
'name': 'Extension name',
|
|
||||||
'support': 'https://example.com/',
|
|
||||||
'website': 'https://example.com/',
|
|
||||||
},
|
|
||||||
).extension
|
).extension
|
||||||
self.assertEqual(entries_for(self.extension).count(), 0)
|
self.assertEqual(entries_for(self.extension).count(), 0)
|
||||||
self.assertIsNone(self.extension.date_approved)
|
self.assertIsNone(self.extension.date_approved)
|
||||||
@ -47,7 +38,7 @@ class ExtensionTest(TestCase):
|
|||||||
'new_state': {'status': 'Approved'},
|
'new_state': {'status': 'Approved'},
|
||||||
'object': '<Extension: Add-on "Extension name">',
|
'object': '<Extension: Add-on "Extension name">',
|
||||||
'old_state': {
|
'old_state': {
|
||||||
'description': 'Extension description',
|
'description': '',
|
||||||
'website': 'https://example.com/',
|
'website': 'https://example.com/',
|
||||||
'name': 'Extension name',
|
'name': 'Extension name',
|
||||||
'status': 1,
|
'status': 1,
|
||||||
@ -57,13 +48,6 @@ class ExtensionTest(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_status_change_updates_date_creates_log_entry(self):
|
|
||||||
self.extension.approve()
|
|
||||||
|
|
||||||
self.assertIsNotNone(self.extension.date_approved)
|
|
||||||
self.assertIsNotNone(self.extension.date_status_changed)
|
|
||||||
self._check_change_message()
|
|
||||||
|
|
||||||
def test_status_change_updates_date_creates_log_entry_with_update_fields(self):
|
def test_status_change_updates_date_creates_log_entry_with_update_fields(self):
|
||||||
self.extension.approve()
|
self.extension.approve()
|
||||||
|
|
||||||
@ -89,42 +73,16 @@ class VersionTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.version = create_version(
|
self.version = create_version(
|
||||||
blender_version_min='2.83.1',
|
metadata__blender_version_min='2.83.1',
|
||||||
version='1.1.2',
|
metadata__name='Extension name',
|
||||||
extension__description='Extension description',
|
metadata__support='https://example.com/',
|
||||||
extension__website='https://example.com/',
|
metadata__version='1.1.2',
|
||||||
extension__name='Extension name',
|
metadata__website='https://example.com/',
|
||||||
extension__status=Extension.STATUSES.DRAFT,
|
|
||||||
extension__support='https://example.com/',
|
|
||||||
)
|
)
|
||||||
self.assertEqual(entries_for(self.version).count(), 0)
|
self.assertEqual(entries_for(self.version).count(), 0)
|
||||||
# FIXME remove when dropping Version.file field
|
# FIXME remove when dropping Version.file field
|
||||||
self.assertEqual(self.version.file, self.version.files.first())
|
self.assertEqual(self.version.file, self.version.files.first())
|
||||||
|
|
||||||
def _check_change_message(self):
|
|
||||||
entries = entries_for(self.version)
|
|
||||||
self.assertEqual(entries.count(), 1)
|
|
||||||
log_entry = entries.first()
|
|
||||||
change_message = json.loads(log_entry.change_message)
|
|
||||||
self.assertEqual(len(change_message), 1)
|
|
||||||
self.assertDictEqual(
|
|
||||||
change_message[0],
|
|
||||||
{
|
|
||||||
'changed': {
|
|
||||||
'fields': ['status'],
|
|
||||||
'name': 'version',
|
|
||||||
'new_state': {'status': 'Approved'},
|
|
||||||
'object': '<Version: Add-on "Extension name" v1.1.2>',
|
|
||||||
'old_state': {
|
|
||||||
'blender_version_max': None,
|
|
||||||
'blender_version_min': '4.2.0',
|
|
||||||
'status': 1,
|
|
||||||
'version': '1.1.2',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_admin_change_view(self):
|
def test_admin_change_view(self):
|
||||||
path = get_admin_change_path(obj=self.version)
|
path = get_admin_change_path(obj=self.version)
|
||||||
self.assertEqual(path, '/admin/extensions/version/1/change/')
|
self.assertEqual(path, '/admin/extensions/version/1/change/')
|
||||||
@ -142,27 +100,18 @@ class UpdateMetadataTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.first_version = create_version(
|
self.first_version = create_version(
|
||||||
extension__description='Extension description',
|
metadata__name='name',
|
||||||
extension__name='name',
|
metadata__support='https://example.com/',
|
||||||
extension__status=Extension.STATUSES.DRAFT,
|
metadata__website='https://example.com/',
|
||||||
extension__support='https://example.com/',
|
|
||||||
extension__website='https://example.com/',
|
|
||||||
file__metadata={
|
|
||||||
'name': 'name',
|
|
||||||
'support': 'https://example.com/',
|
|
||||||
'website': 'https://example.com/',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
self.extension = self.first_version.extension
|
self.extension = self.first_version.extension
|
||||||
|
|
||||||
def test_version_create_and_delete(self):
|
def test_version_create_and_delete(self):
|
||||||
second_version = create_version(
|
second_version = create_version(
|
||||||
extension=self.extension,
|
extension=self.extension,
|
||||||
file__metadata={
|
metadata__name='new name',
|
||||||
'name': 'new name',
|
metadata__support='https://example.com/new',
|
||||||
'support': 'https://example.com/new',
|
metadata__website='https://example.com/new',
|
||||||
'website': 'https://example.com/new',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
self.extension.refresh_from_db()
|
self.extension.refresh_from_db()
|
||||||
self.assertEqual(self.extension.name, 'new name')
|
self.assertEqual(self.extension.name, 'new name')
|
||||||
@ -178,26 +127,16 @@ class UpdateMetadataTest(TestCase):
|
|||||||
def test_old_name_taken(self):
|
def test_old_name_taken(self):
|
||||||
second_version = create_version(
|
second_version = create_version(
|
||||||
extension=self.extension,
|
extension=self.extension,
|
||||||
file__metadata={
|
metadata__name='new name',
|
||||||
'name': 'new name',
|
metadata__support='https://example.com/new',
|
||||||
'support': 'https://example.com/new',
|
metadata__website='https://example.com/new',
|
||||||
'website': 'https://example.com/new',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# another extension uses old name
|
# another extension uses old name
|
||||||
create_version(
|
create_version(
|
||||||
extension__description='Extension description',
|
metadata__name='name',
|
||||||
extension__extension_id='lalalala',
|
metadata__support='https://example.com/',
|
||||||
extension__name='name',
|
metadata__website='https://example.com/',
|
||||||
extension__status=Extension.STATUSES.DRAFT,
|
|
||||||
extension__support='https://example.com/',
|
|
||||||
extension__website='https://example.com/',
|
|
||||||
file__metadata={
|
|
||||||
'name': 'name',
|
|
||||||
'support': 'https://example.com/',
|
|
||||||
'website': 'https://example.com/',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
second_version.delete()
|
second_version.delete()
|
||||||
|
@ -294,16 +294,7 @@ class SubmitFinaliseTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
hash=file_data['file_hash'],
|
hash=file_data['file_hash'],
|
||||||
metadata=file_data['metadata'],
|
metadata=file_data['metadata'],
|
||||||
)
|
)
|
||||||
self.version = create_version(
|
self.version = create_version(file=self.file)
|
||||||
file=self.file,
|
|
||||||
extension__name=file_data['metadata']['name'],
|
|
||||||
extension__slug=file_data['metadata']['id'].replace("_", "-"),
|
|
||||||
extension__website=None,
|
|
||||||
tagline=file_data['metadata']['tagline'],
|
|
||||||
version=file_data['metadata']['version'],
|
|
||||||
blender_version_min=file_data['metadata']['blender_version_min'],
|
|
||||||
schema_version=file_data['metadata']['schema_version'],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_finalise_addon_redirects_if_anonymous(self):
|
def test_get_finalise_addon_redirects_if_anonymous(self):
|
||||||
response = self.client.post(self.file.legacy_version.extension.get_draft_url(), {})
|
response = self.client.post(self.file.legacy_version.extension.get_draft_url(), {})
|
||||||
@ -492,7 +483,7 @@ class NewVersionTest(TestCase):
|
|||||||
fixtures = ['licenses']
|
fixtures = ['licenses']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.version = create_version(extension__extension_id='amaranth')
|
self.version = create_version(metadata__id='amaranth')
|
||||||
self.extension = self.version.extension
|
self.extension = self.version.extension
|
||||||
self.url = self.extension.get_new_version_url()
|
self.url = self.extension.get_new_version_url()
|
||||||
|
|
||||||
@ -541,6 +532,7 @@ class NewVersionTest(TestCase):
|
|||||||
|
|
||||||
def test_upload_new_file_and_finalise_new_version(self):
|
def test_upload_new_file_and_finalise_new_version(self):
|
||||||
self.client.force_login(self.version.file.user)
|
self.client.force_login(self.version.file.user)
|
||||||
|
self.extension.approve()
|
||||||
|
|
||||||
# Check step 1: upload a new file
|
# Check step 1: upload a new file
|
||||||
with open(TEST_FILES_DIR / 'amaranth-1.0.8.zip', 'rb') as fp:
|
with open(TEST_FILES_DIR / 'amaranth-1.0.8.zip', 'rb') as fp:
|
||||||
@ -552,17 +544,22 @@ class NewVersionTest(TestCase):
|
|||||||
response['Location'],
|
response['Location'],
|
||||||
f'/add-ons/{self.extension.slug}/manage/versions/new/{file.pk}/',
|
f'/add-ons/{self.extension.slug}/manage/versions/new/{file.pk}/',
|
||||||
)
|
)
|
||||||
self.assertEqual(self.extension.versions.count(), 1)
|
# now a file upload creates a corresponding version object immediately
|
||||||
self.extension.approve()
|
self.assertEqual(self.extension.versions.count(), 2)
|
||||||
|
new_version = self.extension.versions.order_by('date_created').last()
|
||||||
|
self.assertEqual(new_version.version, '1.0.8')
|
||||||
|
self.assertEqual(new_version.blender_version_min, '4.2.0')
|
||||||
|
self.assertEqual(new_version.schema_version, '1.0.0')
|
||||||
|
self.assertEqual(new_version.file.get_status_display(), 'Approved')
|
||||||
|
self.assertEqual(new_version.release_notes, '')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ApprovalActivity.objects.filter(
|
ApprovalActivity.objects.filter(
|
||||||
extension=self.extension,
|
extension=self.extension,
|
||||||
type=ApprovalActivity.ActivityType.UPLOADED_NEW_VERSION,
|
type=ApprovalActivity.ActivityType.UPLOADED_NEW_VERSION,
|
||||||
).count(),
|
).count(),
|
||||||
0,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check step 2: finalise new version and send to review
|
|
||||||
url = response['Location']
|
url = response['Location']
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
url,
|
url,
|
||||||
@ -572,28 +569,17 @@ class NewVersionTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(self.extension.versions.count(), 2)
|
|
||||||
self.assertEqual(response['Location'], f'/add-ons/{self.extension.slug}/manage/versions/')
|
self.assertEqual(response['Location'], f'/add-ons/{self.extension.slug}/manage/versions/')
|
||||||
new_version = self.extension.versions.order_by('date_created').last()
|
self.assertEqual(self.extension.versions.count(), 2)
|
||||||
self.assertEqual(new_version.version, '1.0.8')
|
new_version.refresh_from_db()
|
||||||
self.assertEqual(new_version.blender_version_min, '4.2.0')
|
|
||||||
self.assertEqual(new_version.schema_version, '1.0.0')
|
|
||||||
self.assertEqual(new_version.release_notes, 'new version')
|
self.assertEqual(new_version.release_notes, 'new version')
|
||||||
self.assertEqual(new_version.file.get_status_display(), 'Approved')
|
|
||||||
self.assertEqual(
|
|
||||||
ApprovalActivity.objects.filter(
|
|
||||||
extension=self.extension,
|
|
||||||
type=ApprovalActivity.ActivityType.UPLOADED_NEW_VERSION,
|
|
||||||
).count(),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DraftsWarningTest(TestCase):
|
class DraftsWarningTest(TestCase):
|
||||||
fixtures = ['licenses']
|
fixtures = ['licenses']
|
||||||
|
|
||||||
def test_page_contains_warning(self):
|
def test_page_contains_warning(self):
|
||||||
version = create_version(extension__extension_id='draft_warning')
|
version = create_version()
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
self.assertEqual(extension.status, Extension.STATUSES.DRAFT)
|
self.assertEqual(extension.status, Extension.STATUSES.DRAFT)
|
||||||
self.client.force_login(extension.authors.all()[0])
|
self.client.force_login(extension.authors.all()[0])
|
||||||
|
@ -293,7 +293,8 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
source='file/original_image_source.jpg',
|
source='file/original_image_source.jpg',
|
||||||
)
|
)
|
||||||
extension = create_approved_version().extension
|
extension = create_approved_version().extension
|
||||||
another_extension = create_approved_version(extension__previews=[file]).extension
|
another_extension = create_approved_version().extension
|
||||||
|
another_extension.previews.add(file)
|
||||||
images_count_before = File.objects.filter(type=File.TYPES.IMAGE).count()
|
images_count_before = File.objects.filter(type=File.TYPES.IMAGE).count()
|
||||||
self.assertEqual(extension.previews.count(), 0)
|
self.assertEqual(extension.previews.count(), 0)
|
||||||
self.assertEqual(another_extension.previews.count(), 1)
|
self.assertEqual(another_extension.previews.count(), 1)
|
||||||
@ -440,11 +441,9 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_update_icon_changes_expected_file_fields(self):
|
def test_update_icon_changes_expected_file_fields(self):
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__icon=ImageFactory(
|
extension.icon = ImageFactory(original_name='old_icon.png')
|
||||||
original_name='old_icon.png',
|
extension.save(update_fields={'icon'})
|
||||||
),
|
|
||||||
).extension
|
|
||||||
self._test_file_properties(
|
self._test_file_properties(
|
||||||
extension.icon,
|
extension.icon,
|
||||||
content_type='image/png',
|
content_type='image/png',
|
||||||
@ -516,7 +515,9 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
source='test_icon_0001.png',
|
source='test_icon_0001.png',
|
||||||
)
|
)
|
||||||
extension = create_approved_version().extension
|
extension = create_approved_version().extension
|
||||||
another_extension = create_approved_version(extension__icon=file).extension
|
another_extension = create_approved_version().extension
|
||||||
|
another_extension.icon = file
|
||||||
|
another_extension.save(update_fields={'icon'})
|
||||||
images_count_before = File.objects.filter(type=File.TYPES.IMAGE).count()
|
images_count_before = File.objects.filter(type=File.TYPES.IMAGE).count()
|
||||||
self.assertIsNone(extension.icon)
|
self.assertIsNone(extension.icon)
|
||||||
self.assertEqual(another_extension.icon_id, file.pk)
|
self.assertEqual(another_extension.icon_id, file.pk)
|
||||||
@ -542,11 +543,9 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
self.assertEqual(file.user, old_user)
|
self.assertEqual(file.user, old_user)
|
||||||
|
|
||||||
def test_update_featured_image_changes_expected_file_fields(self):
|
def test_update_featured_image_changes_expected_file_fields(self):
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__featured_image=ImageFactory(
|
extension.featured_image = ImageFactory(original_name='old_featured_image.png')
|
||||||
original_name='old_featured_image.png',
|
extension.save(update_fields={'featured_image'})
|
||||||
),
|
|
||||||
).extension
|
|
||||||
self._test_file_properties(
|
self._test_file_properties(
|
||||||
extension.featured_image,
|
extension.featured_image,
|
||||||
content_type='image/png',
|
content_type='image/png',
|
||||||
@ -582,8 +581,10 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_convert_to_draft(self):
|
def test_convert_to_draft(self):
|
||||||
version = create_version(extension__status=Extension.STATUSES.AWAITING_REVIEW)
|
version = create_version()
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
|
extension.status = Extension.STATUSES.AWAITING_REVIEW
|
||||||
|
extension.save(update_fields={'status'})
|
||||||
url = extension.get_manage_url()
|
url = extension.get_manage_url()
|
||||||
user = extension.authors.first()
|
user = extension.authors.first()
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
@ -607,9 +608,8 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
self.assertEqual(response3['Location'], extension.get_draft_url())
|
self.assertEqual(response3['Location'], extension.get_draft_url())
|
||||||
|
|
||||||
def test_team_field_in_draft_form(self):
|
def test_team_field_in_draft_form(self):
|
||||||
version = create_version(
|
# default status is DRAFT
|
||||||
extension__status=Extension.STATUSES.DRAFT,
|
version = create_version()
|
||||||
)
|
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
author = extension.authors.first()
|
author = extension.authors.first()
|
||||||
self.client.force_login(author)
|
self.client.force_login(author)
|
||||||
@ -670,10 +670,10 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
|
|||||||
|
|
||||||
def test_team_field_in_update_form(self):
|
def test_team_field_in_update_form(self):
|
||||||
"""This test is a copy-paste of the one above, only status, url and form data differ."""
|
"""This test is a copy-paste of the one above, only status, url and form data differ."""
|
||||||
version = create_version(
|
version = create_version()
|
||||||
extension__status=Extension.STATUSES.APPROVED,
|
|
||||||
)
|
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
|
extension.status = Extension.STATUSES.APPROVED
|
||||||
|
extension.save(update_fields={'status'})
|
||||||
author = extension.authors.first()
|
author = extension.authors.first()
|
||||||
self.client.force_login(author)
|
self.client.force_login(author)
|
||||||
|
|
||||||
|
@ -4,26 +4,20 @@ from django.urls import reverse
|
|||||||
from common.tests.factories.extensions import create_version, create_approved_version
|
from common.tests.factories.extensions import create_version, create_approved_version
|
||||||
from common.tests.factories.teams import TeamFactory
|
from common.tests.factories.teams import TeamFactory
|
||||||
from common.tests.factories.users import UserFactory, create_moderator
|
from common.tests.factories.users import UserFactory, create_moderator
|
||||||
from extensions.models import Extension
|
|
||||||
from teams.models import TeamsUsers
|
from teams.models import TeamsUsers
|
||||||
|
|
||||||
|
|
||||||
def _create_extension():
|
def _create_extension():
|
||||||
return create_version(
|
extension = create_version(
|
||||||
version='1.3.4',
|
metadata__blender_version_min='4.2.0',
|
||||||
blender_version_min='4.2.0',
|
metadata__name='Test Add-on',
|
||||||
extension__name='Test Add-on',
|
metadata__support='https://example.com/issues/',
|
||||||
extension__description='**Description in bold**',
|
metadata__version='1.3.4',
|
||||||
extension__support='https://example.com/issues/',
|
metadata__website='https://example.com/',
|
||||||
extension__website='https://example.com/',
|
|
||||||
extension__status=Extension.STATUSES.DRAFT,
|
|
||||||
extension__average_score=2.5,
|
|
||||||
file__metadata={
|
|
||||||
'name': 'Test Add-on',
|
|
||||||
'support': 'https://example.com/issues/',
|
|
||||||
'website': 'https://example.com/',
|
|
||||||
},
|
|
||||||
).extension
|
).extension
|
||||||
|
extension.description = '**Description in bold**'
|
||||||
|
extension.save(update_fields={'description'})
|
||||||
|
return extension
|
||||||
|
|
||||||
|
|
||||||
class _BaseTestCase(TestCase):
|
class _BaseTestCase(TestCase):
|
||||||
@ -59,7 +53,8 @@ class PublicViewsTest(_BaseTestCase):
|
|||||||
self.assertTemplateUsed(response, 'extensions/home.html')
|
self.assertTemplateUsed(response, 'extensions/home.html')
|
||||||
|
|
||||||
def test_no_one_can_view_extension_page_when_not_listed_404(self):
|
def test_no_one_can_view_extension_page_when_not_listed_404(self):
|
||||||
extension = create_version(extension__is_listed=False).extension
|
# not listed by default
|
||||||
|
extension = create_version().extension
|
||||||
|
|
||||||
moderator = create_moderator()
|
moderator = create_moderator()
|
||||||
staff = UserFactory(is_staff=True)
|
staff = UserFactory(is_staff=True)
|
||||||
|
@ -10,7 +10,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from common.compare import is_in_version_range, version
|
from common.compare import is_in_version_range, version
|
||||||
from extensions.models import Extension, Platform, Version
|
from extensions.models import Extension, Platform
|
||||||
from extensions.utils import clean_json_dictionary_from_optional_fields
|
from extensions.utils import clean_json_dictionary_from_optional_fields
|
||||||
from extensions.views.manage import NewVersionView
|
from extensions.views.manage import NewVersionView
|
||||||
from files.forms import FileFormSkipAgreed
|
from files.forms import FileFormSkipAgreed
|
||||||
@ -217,13 +217,10 @@ class UploadExtensionVersionView(APIView):
|
|||||||
file_instance.user = user
|
file_instance.user = user
|
||||||
file_instance.save()
|
file_instance.save()
|
||||||
|
|
||||||
# Create the version from the file
|
version = extension.create_version_from_file(
|
||||||
version = Version.objects.update_or_create(
|
|
||||||
extension=extension,
|
|
||||||
file=file_instance,
|
file=file_instance,
|
||||||
release_notes=release_notes,
|
release_notes=release_notes,
|
||||||
**file_instance.parsed_version_fields,
|
)
|
||||||
)[0]
|
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
|
@ -219,6 +219,12 @@ class NewVersionView(
|
|||||||
kwargs['extension'] = self.extension
|
kwargs['extension'] = self.extension
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def form_valid(self, form):
|
||||||
|
response = super().form_valid(form)
|
||||||
|
self.extension.create_version_from_file(self.object)
|
||||||
|
return response
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
'extensions:new-version-finalise',
|
'extensions:new-version-finalise',
|
||||||
@ -236,17 +242,11 @@ class NewVersionFinalizeView(LoginRequiredMixin, OwnsFileMixin, CreateView):
|
|||||||
template_name = 'extensions/new_version_finalise.html'
|
template_name = 'extensions/new_version_finalise.html'
|
||||||
form_class = VersionForm
|
form_class = VersionForm
|
||||||
|
|
||||||
def _get_extension(self) -> 'Extension':
|
|
||||||
return get_object_or_404(Extension, slug=self.kwargs['slug'])
|
|
||||||
|
|
||||||
def _get_version(self, extension) -> 'Version':
|
|
||||||
return Version.objects.update_or_create(
|
|
||||||
extension=extension, file=self.file, **self.file.parsed_version_fields
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
form_kwargs = super().get_form_kwargs()
|
form_kwargs = super().get_form_kwargs()
|
||||||
form_kwargs['instance'] = self._get_version(self.extension)
|
# this lookup via VersionFiles ManyToManyManager returns the version that was created on
|
||||||
|
# the previous step by create_version_from_file
|
||||||
|
form_kwargs['instance'] = self.file.version.first()
|
||||||
return form_kwargs
|
return form_kwargs
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
@ -257,7 +257,7 @@ class NewVersionFinalizeView(LoginRequiredMixin, OwnsFileMixin, CreateView):
|
|||||||
return initial
|
return initial
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return self.extension.get_manage_versions_url()
|
return self.object.extension.get_manage_versions_url()
|
||||||
|
|
||||||
|
|
||||||
class UpdateVersionView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
class UpdateVersionView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
||||||
|
@ -8,7 +8,6 @@ from files.models import File
|
|||||||
class OwnsFileMixin(UserPassesTestMixin):
|
class OwnsFileMixin(UserPassesTestMixin):
|
||||||
def dispatch(self, *args, **kwargs):
|
def dispatch(self, *args, **kwargs):
|
||||||
self.file = get_object_or_404(File, pk=self.kwargs['pk'])
|
self.file = get_object_or_404(File, pk=self.kwargs['pk'])
|
||||||
self.extension = self._get_extension()
|
|
||||||
return super().dispatch(*args, **kwargs)
|
return super().dispatch(*args, **kwargs)
|
||||||
|
|
||||||
def test_func(self) -> bool:
|
def test_func(self) -> bool:
|
||||||
|
@ -4,7 +4,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
|
|
||||||
from extensions.models import Version, Extension
|
from extensions.models import Extension
|
||||||
from files.forms import FileForm
|
from files.forms import FileForm
|
||||||
from files.models import File
|
from files.models import File
|
||||||
|
|
||||||
@ -35,37 +35,7 @@ class UploadFileView(LoginRequiredMixin, CreateView):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Create an extension and a version already, associated with the user."""
|
"""Create an extension and a version already, associated with the user."""
|
||||||
self.file = form.instance
|
file = form.save()
|
||||||
|
self.extension = Extension.create_from_file(file)
|
||||||
parsed_extension_fields = self.file.parsed_extension_fields
|
self.extension.create_version_from_file(file)
|
||||||
if parsed_extension_fields:
|
|
||||||
# Try to look up extension by the same author and file info
|
|
||||||
extension = (
|
|
||||||
Extension.objects.authored_by(self.request.user)
|
|
||||||
.filter(type=self.file.type, **parsed_extension_fields)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if extension:
|
|
||||||
logger.warning(
|
|
||||||
'Found existing extension pk=%s for file pk=%s',
|
|
||||||
extension.pk,
|
|
||||||
self.file.pk,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Make sure an extension has a user associated to it from the beginning, otherwise
|
|
||||||
# it will prevent it from being re-uploaded and yet not show on My Extensions.
|
|
||||||
self.extension = Extension.objects.update_or_create(
|
|
||||||
type=self.file.type, **parsed_extension_fields
|
|
||||||
)[0]
|
|
||||||
self.extension.authors.add(self.request.user)
|
|
||||||
self.extension.save()
|
|
||||||
|
|
||||||
# Need to save the form to be able to use the file to create the version.
|
|
||||||
self.object = self.file = form.save()
|
|
||||||
|
|
||||||
Version.objects.update_or_create(
|
|
||||||
extension=self.extension, file=self.file, **self.file.parsed_version_fields
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
@ -169,9 +169,11 @@ class File(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|||||||
extension_id = data.get('id')
|
extension_id = data.get('id')
|
||||||
name = data.get('name', self.original_name)
|
name = data.get('name', self.original_name)
|
||||||
return {
|
return {
|
||||||
|
'extension_id': extension_id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'slug': utils.slugify(extension_id),
|
'slug': utils.slugify(extension_id),
|
||||||
'extension_id': extension_id,
|
'support': data.get('support'),
|
||||||
|
'type': self.type,
|
||||||
'website': data.get('website'),
|
'website': data.get('website'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,11 @@ class FileTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.file = FileFactory(
|
self.file = FileFactory(
|
||||||
status=File.STATUSES.AWAITING_REVIEW,
|
|
||||||
original_name='test.zip',
|
|
||||||
hash='foobar',
|
hash='foobar',
|
||||||
|
metadata={},
|
||||||
|
original_name='test.zip',
|
||||||
size_bytes=7149,
|
size_bytes=7149,
|
||||||
|
status=File.STATUSES.AWAITING_REVIEW,
|
||||||
)
|
)
|
||||||
self.assertEqual(entries_for(self.file).count(), 0)
|
self.assertEqual(entries_for(self.file).count(), 0)
|
||||||
self.assertIsNone(self.file.date_approved)
|
self.assertIsNone(self.file.date_approved)
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
|
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
from common.tests.factories.files import FileFactory, ImageFactory
|
from common.tests.factories.files import FileFactory, ImageFactory, VideoFactory
|
||||||
from files.tasks import make_thumbnails
|
from files.tasks import make_thumbnails
|
||||||
import files.models
|
import files.models
|
||||||
|
|
||||||
@ -96,9 +96,7 @@ class TasksTest(TestCase):
|
|||||||
@patch('files.utils.Image')
|
@patch('files.utils.Image')
|
||||||
@patch('files.utils.FFmpeg')
|
@patch('files.utils.FFmpeg')
|
||||||
def test_make_thumbnails_for_video(self, mock_ffmpeg, mock_image, mock_resize_image):
|
def test_make_thumbnails_for_video(self, mock_ffmpeg, mock_image, mock_resize_image):
|
||||||
file = FileFactory(
|
file = VideoFactory(hash='deadbeef')
|
||||||
hash='deadbeef', source='file/path.mp4', type=files.models.File.TYPES.VIDEO
|
|
||||||
)
|
|
||||||
files.models.FileValidation.objects.create(file=file, is_ok=True, results={})
|
files.models.FileValidation.objects.create(file=file, is_ok=True, results={})
|
||||||
self.assertIsNone(file.thumbnail.name)
|
self.assertIsNone(file.thumbnail.name)
|
||||||
self.assertEqual(file.metadata, {})
|
self.assertEqual(file.metadata, {})
|
||||||
|
@ -9,7 +9,7 @@ class RatingsViewTest(TestCase):
|
|||||||
fixtures = ['dev', 'licenses']
|
fixtures = ['dev', 'licenses']
|
||||||
|
|
||||||
def test_get_anonymous(self):
|
def test_get_anonymous(self):
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
[
|
[
|
||||||
RatingFactory(
|
RatingFactory(
|
||||||
version=version, text='this rating is rejected', status=Rating.STATUSES.REJECTED
|
version=version, text='this rating is rejected', status=Rating.STATUSES.REJECTED
|
||||||
@ -40,7 +40,7 @@ class RatingsViewTest(TestCase):
|
|||||||
self.assertNotContains(response, 'this rating is deleted', html=True)
|
self.assertNotContains(response, 'this rating is deleted', html=True)
|
||||||
|
|
||||||
def test_get_logged_in_can_see_own_unlisted_rating(self):
|
def test_get_logged_in_can_see_own_unlisted_rating(self):
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
user = UserFactory()
|
user = UserFactory()
|
||||||
[
|
[
|
||||||
RatingFactory(
|
RatingFactory(
|
||||||
@ -73,7 +73,7 @@ class AddRatingViewTest(TestCase):
|
|||||||
fixtures = ['dev', 'licenses']
|
fixtures = ['dev', 'licenses']
|
||||||
|
|
||||||
def test_get_anonymous_redirects_to_login(self):
|
def test_get_anonymous_redirects_to_login(self):
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
|
|
||||||
url = version.extension.get_rate_url()
|
url = version.extension.get_rate_url()
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
@ -82,7 +82,7 @@ class AddRatingViewTest(TestCase):
|
|||||||
self.assertTrue(response['Location'].startswith('/oauth/login'))
|
self.assertTrue(response['Location'].startswith('/oauth/login'))
|
||||||
|
|
||||||
def test_get_logged_in_as_maintainer_cant_rate(self):
|
def test_get_logged_in_as_maintainer_cant_rate(self):
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
|
|
||||||
url = version.extension.get_rate_url()
|
url = version.extension.get_rate_url()
|
||||||
self.client.force_login(version.extension.authors.first())
|
self.client.force_login(version.extension.authors.first())
|
||||||
@ -93,7 +93,7 @@ class AddRatingViewTest(TestCase):
|
|||||||
|
|
||||||
def test_get_logged_in_can_rate(self):
|
def test_get_logged_in_can_rate(self):
|
||||||
user = UserFactory()
|
user = UserFactory()
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
|
|
||||||
url = version.extension.get_rate_url()
|
url = version.extension.get_rate_url()
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
@ -104,7 +104,7 @@ class AddRatingViewTest(TestCase):
|
|||||||
|
|
||||||
def test_post_logged_in_validation_errors(self):
|
def test_post_logged_in_validation_errors(self):
|
||||||
user = UserFactory()
|
user = UserFactory()
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
url = version.extension.get_rate_url()
|
url = version.extension.get_rate_url()
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ class AddRatingViewTest(TestCase):
|
|||||||
|
|
||||||
def test_post_logged_in_adds_new_rating(self):
|
def test_post_logged_in_adds_new_rating(self):
|
||||||
user = UserFactory()
|
user = UserFactory()
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
extension = version.extension
|
extension = version.extension
|
||||||
self.assertEqual(Rating.objects.count(), 0)
|
self.assertEqual(Rating.objects.count(), 0)
|
||||||
self.assertEqual(extension.ratings.count(), 0)
|
self.assertEqual(extension.ratings.count(), 0)
|
||||||
@ -165,7 +165,7 @@ class AddRatingViewTest(TestCase):
|
|||||||
self.assertEqual(rating.text, text)
|
self.assertEqual(rating.text, text)
|
||||||
|
|
||||||
def test_reply(self):
|
def test_reply(self):
|
||||||
version = create_approved_version(ratings=[])
|
version = create_approved_version()
|
||||||
rating = RatingFactory(version=version, text='some text', status=Rating.STATUSES.APPROVED)
|
rating = RatingFactory(version=version, text='some text', status=Rating.STATUSES.APPROVED)
|
||||||
|
|
||||||
random_user = UserFactory()
|
random_user = UserFactory()
|
||||||
|
@ -10,7 +10,7 @@ class CommentsViewTest(TestCase):
|
|||||||
fixtures = ['licenses']
|
fixtures = ['licenses']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
version = create_version(file__status=File.STATUSES.AWAITING_REVIEW)
|
version = create_version(status=File.STATUSES.AWAITING_REVIEW)
|
||||||
self.default_version = version
|
self.default_version = version
|
||||||
ApprovalActivity(
|
ApprovalActivity(
|
||||||
type=ApprovalActivity.ActivityType.COMMENT,
|
type=ApprovalActivity.ActivityType.COMMENT,
|
||||||
|
@ -6,17 +6,13 @@ from django.test import TestCase
|
|||||||
from common.tests.factories.extensions import create_approved_version
|
from common.tests.factories.extensions import create_approved_version
|
||||||
from stats.models import ExtensionView, ExtensionDownload, ExtensionCountedStat
|
from stats.models import ExtensionView, ExtensionDownload, ExtensionCountedStat
|
||||||
|
|
||||||
# TODO: tests for VersionFactory Version download_count
|
|
||||||
|
|
||||||
|
|
||||||
class WriteStatsCommandTest(TestCase):
|
class WriteStatsCommandTest(TestCase):
|
||||||
fixtures = ['dev', 'licenses']
|
fixtures = ['dev', 'licenses']
|
||||||
|
|
||||||
def test_command_updates_extensions_view_counters(self):
|
def test_command_updates_extensions_view_counters(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__view_count=0, extension__download_count=0
|
|
||||||
).extension
|
|
||||||
ExtensionView.objects.bulk_create(
|
ExtensionView.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ExtensionView(extension_id=extension.pk, ip_address='192.19.10.10'),
|
ExtensionView(extension_id=extension.pk, ip_address='192.19.10.10'),
|
||||||
@ -44,9 +40,7 @@ class WriteStatsCommandTest(TestCase):
|
|||||||
|
|
||||||
def test_command_updates_extensions_download_counters(self):
|
def test_command_updates_extensions_download_counters(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__view_count=0, extension__download_count=0
|
|
||||||
).extension
|
|
||||||
ExtensionDownload.objects.bulk_create(
|
ExtensionDownload.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ExtensionDownload(extension_id=extension.pk, ip_address='192.19.10.10'),
|
ExtensionDownload(extension_id=extension.pk, ip_address='192.19.10.10'),
|
||||||
@ -74,9 +68,9 @@ class WriteStatsCommandTest(TestCase):
|
|||||||
|
|
||||||
def test_command_adds_extensions_view_counters(self):
|
def test_command_adds_extensions_view_counters(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__view_count=10, extension__download_count=0
|
extension.view_count = 10
|
||||||
).extension
|
extension.save(update_fields={'view_count'})
|
||||||
ExtensionView.objects.bulk_create(
|
ExtensionView.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ExtensionView(extension_id=extension.pk, ip_address='192.19.10.10'),
|
ExtensionView(extension_id=extension.pk, ip_address='192.19.10.10'),
|
||||||
@ -104,9 +98,9 @@ class WriteStatsCommandTest(TestCase):
|
|||||||
|
|
||||||
def test_command_adds_extensions_download_counters(self):
|
def test_command_adds_extensions_download_counters(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__view_count=0, extension__download_count=10
|
extension.download_count = 10
|
||||||
).extension
|
extension.save(update_fields={'download_count'})
|
||||||
ExtensionDownload.objects.bulk_create(
|
ExtensionDownload.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ExtensionDownload(extension_id=extension.pk, ip_address='192.19.10.10'),
|
ExtensionDownload(extension_id=extension.pk, ip_address='192.19.10.10'),
|
||||||
@ -134,9 +128,10 @@ class WriteStatsCommandTest(TestCase):
|
|||||||
|
|
||||||
def test_command_updates_extensions_both_download_and_view_counters(self):
|
def test_command_updates_extensions_both_download_and_view_counters(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__view_count=4, extension__download_count=5
|
extension.download_count = 5
|
||||||
).extension
|
extension.view_count = 4
|
||||||
|
extension.save(update_fields={'download_count', 'view_count'})
|
||||||
ExtensionView.objects.bulk_create(
|
ExtensionView.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ExtensionView(extension_id=extension.pk, ip_address='192.19.10.10'),
|
ExtensionView(extension_id=extension.pk, ip_address='192.19.10.10'),
|
||||||
@ -174,10 +169,10 @@ class WriteStatsCommandTest(TestCase):
|
|||||||
def test_command_updates_extensions_both_download_and_view_counters_uses_last_seen_id(self):
|
def test_command_updates_extensions_both_download_and_view_counters_uses_last_seen_id(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
initial_view_count, initial_download_count = 4, 5
|
initial_view_count, initial_download_count = 4, 5
|
||||||
extension = create_approved_version(
|
extension = create_approved_version().extension
|
||||||
extension__download_count=initial_download_count,
|
extension.download_count = initial_download_count
|
||||||
extension__view_count=initial_view_count,
|
extension.view_count = initial_view_count
|
||||||
).extension
|
extension.save(update_fields={'download_count', 'view_count'})
|
||||||
download_count_starts_here = ExtensionDownload(
|
download_count_starts_here = ExtensionDownload(
|
||||||
extension_id=extension.pk, ip_address='192.19.10.15'
|
extension_id=extension.pk, ip_address='192.19.10.15'
|
||||||
)
|
)
|
||||||
|
@ -33,14 +33,12 @@ class TestTasks(TestCase):
|
|||||||
# Abuse reported by someone else ABOUT this account:
|
# Abuse reported by someone else ABOUT this account:
|
||||||
self.report3 = AbuseReportFactory(user=user)
|
self.report3 = AbuseReportFactory(user=user)
|
||||||
self.report4 = AbuseReportFactory(user=user)
|
self.report4 = AbuseReportFactory(user=user)
|
||||||
self.authored_unlisted_extension = create_version(
|
self.authored_unlisted_extension = create_version(user=user).extension
|
||||||
file__user=user, extension__is_listed=False
|
|
||||||
).extension
|
|
||||||
self.assertFalse(self.authored_unlisted_extension.is_listed)
|
self.assertFalse(self.authored_unlisted_extension.is_listed)
|
||||||
|
|
||||||
def create_account_data_that_cannot_be_deleted(self, user):
|
def create_account_data_that_cannot_be_deleted(self, user):
|
||||||
"""Create objects which prevent account deletion but allow anonymisation."""
|
"""Create objects which prevent account deletion but allow anonymisation."""
|
||||||
self.authored_listed_extension = create_approved_version(file__user=user).extension
|
self.authored_listed_extension = create_approved_version(user=user).extension
|
||||||
self.assertTrue(self.authored_listed_extension.is_listed)
|
self.assertTrue(self.authored_listed_extension.is_listed)
|
||||||
|
|
||||||
def test_handle_deletion_request_anonymized(self):
|
def test_handle_deletion_request_anonymized(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user