Skip to content
Snippets Groups Projects
Commit 5b74cbbc authored by florimondmanca's avatar florimondmanca
Browse files

update management commands, other quick fixes

parent e6fde835
No related branches found
No related tags found
No related merge requests found
oser_backend/api/management/commands/data/visit-1.jpg

59.5 KiB

oser_backend/api/management/commands/data/visit-2.jpg

36 KiB

oser_backend/api/management/commands/data/visit-3.jpg

19.9 KiB

"""Populate the database with fake data."""
import os
import random
from itertools import cycle
from contextlib import contextmanager
from django.core.management.base import BaseCommand
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import transaction
from django.core.files import File
from tests.factory import (ArticleFactory, CategoryFactory, KeyFigureFactory,
StudentFactory, TestimonyFactory, VisitFactory)
from showcase_site.models import Category
from tests.factory import StudentFactory
from tests.factory import ArticleFactory
from tests.factory import CategoryFactory
from tests.factory import TestimonyFactory
HERE = os.path.dirname(os.path.abspath(__file__))
data_path = os.path.join(HERE, 'data')
def article_filename(i):
return 'article-{}.jpg'.format(i)
def count_article_images():
count = 0
while os.path.exists(os.path.join(data_path, article_filename(count))):
count += 1
return count
num_images = count_article_images()
def article_images(number):
images = cycle(range(num_images))
for _ in range(number):
filename = article_filename(next(images))
path = os.path.join(data_path, filename)
with open(path, 'rb') as img:
wrapped_img = File(img)
# Default name of the file is the full path to img.
# Use only the filename.
wrapped_img.name = filename
yield wrapped_img
from .utils import DataLoader, get_model, watcher
class Command(BaseCommand):
"""Populate the database with fake data."""
help = 'Populate the database with fake data.'
affected = list(
map(
get_model,
(StudentFactory, CategoryFactory, ArticleFactory,
TestimonyFactory, KeyFigureFactory,
VisitFactory)
))
def add_arguments(self, parser):
parser.add_argument(
'--flushbefore',
'--cleanbefore',
action='store_true',
dest='flushbefore',
help='Call manage.py flush before populating the database',
dest='cleanbefore',
help='Delete all objects of affected models before populating.',
)
parser.add_argument(
'--clean',
action='store_true',
dest='clean',
help='Remove previously generated objects from database',
help='Delete all objects of affected models and exit.',
)
parser.add_argument(
'--preview',
action='store_true',
dest='preview',
help='Show affected models and exit.'
)
@contextmanager
def creating(self, factory_class, amount):
"""Automatically print a line with number of created objects."""
plural = factory_class._meta.model._meta.verbose_name_plural
try:
yield amount
self.stdout.write('Created {} {}.'.format(amount, plural))
finally:
pass
def create_students(self):
with self.creating(StudentFactory, 10) as n:
StudentFactory.create_batch(n)
StudentFactory.create_batch(5)
def create_categories(self):
category_titles = [
......@@ -87,7 +55,6 @@ class Command(BaseCommand):
]
if not category_titles:
return
with self.creating(CategoryFactory, len(category_titles)):
for title in category_titles:
CategoryFactory.create(title=title)
......@@ -103,34 +70,56 @@ class Command(BaseCommand):
.format(categories.count(), article))
def create_articles(self):
with self.creating(ArticleFactory, 5) as n:
for image in article_images(n):
for image in DataLoader('article-{i}.jpg', 5):
article = ArticleFactory.create(image=image)
self.add_random_categories(article)
article.save()
def create_testimonies(self):
with self.creating(TestimonyFactory, 6) as n:
TestimonyFactory.create_batch(n)
TestimonyFactory.create_batch(3)
def create_key_figures(self):
KeyFigureFactory.create_batch(4)
def create_visits(self):
for image in DataLoader('visit-{i}.jpg', 8):
VisitFactory.create(image=image)
@watcher(*affected)
def create(self):
self.create_students()
self.create_categories()
self.create_articles()
self.create_testimonies()
self.create_key_figures()
self.create_visits()
@watcher(*affected)
def _clean(self):
for model in self.affected:
model.objects.all().delete()
def clean(self):
StudentFactory._meta.model.objects.all().delete()
Category.objects.all().delete()
ArticleFactory._meta.model.objects.all().delete()
TestimonyFactory._meta.model.objects.all().delete()
self._clean()
call_command('clean_media')
self.stdout.write(self.style.SUCCESS('Cleaned populated database.'))
def preview(self):
if self.affected:
self.stdout.write(
self.style.NOTICE('The following models will be affected:'))
for model in self.affected:
self.stdout.write(model._meta.label)
def handle(self, *args, **options):
with transaction.atomic():
if options.get('flushbefore', False):
call_command('flush')
if options.get('clean', False):
if options.get('preview'):
self.preview()
elif options.get('clean'):
self.clean()
else:
self.create_students()
self.create_categories()
self.create_articles()
self.create_testimonies()
if options.get('cleanbefore', False):
self.clean()
self.create()
self.stdout.write(self.style.SUCCESS('Populated database.'))
self.check()
"""Utils for populatedb."""
import os
import re
import string
from itertools import cycle
from django.core.files import File
from django.db.models.base import ModelBase
from django.template.defaultfilters import pluralize
from factory.base import FactoryMetaClass
HERE = os.path.dirname(os.path.abspath(__file__))
def format_keys(s):
"""Return tuple of format keys in s.
format_keys('Hi {person}. Eat {food}? {}') => ('person', 'good', '')
"""
formatter = string.Formatter()
return tuple(tup[1] for tup in formatter.parse(s) if tup[1] is not None)
assert format_keys('Hi {person}. Eat {food}? {}') == ('person', 'food', '')
class DataLoader:
"""Iterable that yields filenames to a given amount of resources.
Resources are cycled through if the required amount exceeds
the amount of resources available.
Usage
-----
for filename in DataLoader('resource-{i}.txt', 6):
with open(filename) as text:
print(text)
"""
data_path = os.path.join(HERE, 'data')
def __init__(self, resource_format, amount, path=None):
if path is None:
path = os.path.join(HERE, 'data')
self.pattern = self._make_pattern(resource_format)
self.path = path
self.amount = amount
self.resources = self._find_resources()
@staticmethod
def _make_pattern(fmt):
if 'i' not in format_keys(fmt):
raise ValueError('Resource format {} must contain key "i"'
.format(fmt))
return re.compile('^' + fmt.replace('{i}', '.*') + '$')
def _find_resources(self):
return [f for f in os.listdir(self.path)
if self.pattern.match(f)]
def __iter__(self):
resources = cycle(self.resources)
for _ in range(self.amount):
filename = next(resources)
path = os.path.join(self.path, filename)
with open(path, 'rb') as f:
wrapped_file = File(f)
# Default name of the file is the full path to file.
# Use only the filename.
wrapped_file.name = filename
yield wrapped_file
def get_model(element):
"""Convert element to a Django Model class.
Element can be a Model class or a DjangoModelFactory class.
"""
if isinstance(element, FactoryMetaClass):
return element._meta.model
if not isinstance(element, ModelBase):
raise ValueError(
'Expected Model or DjangoModelFactory, got {}'
.format(type(element)))
return element
def watcher(*watched):
"""Decorator to report changes in amounts of objects in a Command.
Counts number of objects per model before and after the decorated
function is executed, and shows the difference in nicely formatted
messages.
Usage
-----
class MyCommand(BaseCommand):
@watcher(MyModel)
def do_something(self):
... create or delete MyModel instances here ...
Parameters
----------
*watched :
List of Model-like (FactoryBoy DjangoModelFactory also accepted).
"""
watched = list(map(get_model, watched))
def get_counts():
return [(model._meta.verbose_name, model.objects.all().count())
for model in watched]
def decorator(func):
def watched_func(self, *args, **kwargs):
counts_before = get_counts()
rv = func(self, *args, **kwargs)
counts_after = get_counts()
diffs = ((name, after - before)
for (name, after), (name, before)
in zip(counts_after, counts_before))
for name, diff in diffs:
pluralized = name + pluralize(abs(diff))
if diff > 0:
self.stdout.write(
'Created {} {}'.format(diff, pluralized))
elif diff < 0:
self.stdout.write(
'Deleted {} {}'.format(-diff, pluralized))
return rv
return watched_func
return decorator
import os
from django.apps import apps
from django.db.models import Q
from django.db.models import FileField
from django.conf import settings
from django.core.management import BaseCommand
from django.db.models import FileField, Q
class Command(BaseCommand):
......@@ -39,11 +38,11 @@ class Command(BaseCommand):
filters &= Q(**is_null) | Q(**is_empty)
# only retrieve the models which have non-empty, non-null file
# fields
if file_fields:
files = (model.objects.exclude(filters)
.values_list(*file_fields, flat=True)
for field in file_fields:
field_files = (model.objects.exclude(filters)
.values_list(field, flat=True)
.distinct())
db_files.update(files)
db_files.update(field_files)
return db_files
......@@ -76,7 +75,8 @@ class Command(BaseCommand):
if deletables:
unused = len(deletables)
self.stdout.write('Unused media files were detected:')
self.stdout.write(
self.style.NOTICE('Unused media files were detected:'))
self.stdout.write('\n'.join(f_ for f_ in deletables))
for file_ in deletables:
......
......@@ -3,22 +3,21 @@
FactoryBoy docs: http://factoryboy.readthedocs.io/en/latest/index.html
"""
from datetime import timedelta
import pytz
import random
from datetime import timedelta
from string import printable
import factory
import factory.django
from django.utils import timezone
import pytz
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.db.models.signals import post_save
from django.utils import timezone
import showcase_site.models
import tutoring.models
import users.models
import showcase_site.models
import visits.models
from tutoring.utils import random_uai_code
from users.permissions import Groups
......@@ -225,8 +224,8 @@ class KeyFigureFactory(factory.DjangoModelFactory):
class Meta: # noqa
model = showcase_site.models.KeyFigure
figure = factory.LazyFunction(lambda: random.randint(10, 500))
description = factory.Faker('text', max_nb_chars=100)
figure = factory.LazyFunction(lambda: random.randint(10, 200))
description = factory.Faker('text', max_nb_chars=60, locale='fr')
order = factory.Sequence(lambda n: n)
......@@ -238,11 +237,14 @@ class VisitFactory(factory.DjangoModelFactory):
class Meta: # noqa
model = visits.models.Visit
exclude = ('date_random_range', 'deadline_random_range',)
exclude = ('date_random_range', 'deadline_random_range',
'_summary', '_description')
title = factory.Faker('sentence', locale='fr')
summary = factory.Faker('sentences', nb=2, locale='fr')
description = factory.Faker('paragraphs', nb=2, locale='fr')
_summary = factory.Faker('sentences', nb=2, locale='fr')
summary = factory.LazyAttribute(lambda o: ' '.join(o._summary))
_description = factory.Faker('paragraphs', nb=5, locale='fr')
description = factory.LazyAttribute(lambda o: '\n'.join(o._description))
place = factory.Faker('address', locale='fr')
@factory.lazy_attribute
......
......@@ -64,10 +64,17 @@ class VisitParticipantInline(admin.TabularInline):
class VisitAdmin(admin.ModelAdmin):
"""Admin panel for visits."""
# IDEA create a dashboard using:
# https://medium.com/@hakibenita/how-to-turn-django-admin-into-a-lightweight-dashboard-a0e0bbf609ad
form = VisitForm
inlines = (VisitParticipantInline,)
list_display = ('__str__', 'place', 'date', 'deadline',
'_registrations_open', 'fact_sheet')
'_registrations_open', 'num_participants')
list_filter = ('date', RegistrationsOpenFilter)
search_fields = ('title', 'place',)
exclude = ('participants',)
def num_participants(self, obj):
return obj.participants.count()
num_participants.short_description = 'Participants'
"""Visits serializers."""
from django.utils.timezone import now
from rest_framework import serializers
from .models import Visit, VisitParticipant
from users.models import Student
from .models import Visit, VisitParticipant
class VisitSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for Visit."""
participants = serializers.StringRelatedField(many=True)
passed = serializers.SerializerMethodField()
def get_passed(self, obj):
return obj.date < now()
class Meta: # noqa
model = Visit
fields = ('id', 'url', 'title', 'summary', 'description',
'place', 'date', 'deadline',
'registrations_open',
fields = ('id', 'url', 'title', 'summary', 'description', 'place',
'date', 'passed',
'deadline', 'registrations_open',
'participants',
'image', 'fact_sheet',)
extra_kwargs = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment