Commit 2f8759ea authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Merge branch 'document_utils' into 'master'

Clean up and document utils package

Closes #587

See merge request !726
parents 3f0e4c5c fbf0a853
......@@ -48,6 +48,7 @@ docs:
before_script:
# install django deps
- pip install -r requirements.txt
- pip install -r dev-requirements.txt
- cd docs
# install doc deps
- pip install -r requirements.txt
......
......@@ -185,8 +185,11 @@ autodoc_default_flags = ['members', 'undoc-members']
doctest_test_doctest_blocks = ''
# -- intersphinx ---
intersphinx_mapping = {'python': ('https://docs.python.org/3.5', None)}
intersphinx_mapping = {
'python': ('https://docs.python.org/3.6', None),
'django': ('https://docs.djangoproject.com/en/2.0/',
'https://docs.djangoproject.com/en/2.0/_objects/'),
}
# -- Supress warnings ---
suppress_warnings = [
......
utils\.management\.commands package
===================================
.. automodule:: utils.management.commands
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
utils\.management\.commands\.createfixtures module
--------------------------------------------------
.. automodule:: utils.management.commands.createfixtures
:members:
:undoc-members:
:show-inheritance:
utils\.management package
=========================
.. automodule:: utils.management
:members:
:undoc-members:
:show-inheritance:
Subpackages
-----------
.. toctree::
utils.management.commands
......@@ -6,6 +6,14 @@ utils package
:undoc-members:
:show-inheritance:
Subpackages
-----------
.. toctree::
utils.management
utils.templatetags
Submodules
----------
......
utils\.templatetags package
===========================
.. automodule:: utils.templatetags
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
utils\.templatetags\.thumbnail module
-------------------------------------
.. automodule:: utils.templatetags.thumbnail
:members:
:undoc-members:
:show-inheritance:
"""Provides an exception filter for django"""
import logging
from django.views.debug import (SafeExceptionReporterFilter,
CLEANSED_SUBSTITUTE)
logger = logging.getLogger(__name__)
__LOGGER = logging.getLogger(__name__)
class ThaliaSafeExceptionReporterFilter(SafeExceptionReporterFilter):
"""Filter additional variables from tracebacks"""
"""
Filter additional variables from tracebacks
https://docs.djangoproject.com/en/2.0/howto/error-reporting/#filtering-sensitive-information
"""
def get_traceback_frame_variables(self, request, tb_frame):
"""Filter traceback frame variables"""
......@@ -22,6 +27,7 @@ class ThaliaSafeExceptionReporterFilter(SafeExceptionReporterFilter):
val.META['HTTP_COOKIE'] = CLEANSED_SUBSTITUTE
val.META['HTTP_AUTHORIZATION'] = CLEANSED_SUBSTITUTE
except (AttributeError, IndexError):
logger.exception("Somehow cleaning the request failed")
__LOGGER.exception(
"Somehow cleaning the request failed")
return local_vars
"""Empty subpackage for the django management command API"""
"""
Provides the command to generate fixtures
"""
# pylint: disable=invalid-name,no-member,too-few-public-methods
# pylint: disable=attribute-defined-outside-init,no-self-use
import math
import random
import string
......@@ -15,41 +20,52 @@ from partners.models import Partner, Vacancy, VacancyCategory
from pizzas.models import Product
from utils.snippets import datetime_to_lectureyear
import factory
from faker import Factory as FakerFactory
from pydenticon import Generator as IconGenerator
try:
import factory
from faker import Factory as FakerFactory
from pydenticon import Generator as IconGenerator
except ImportError as error:
raise Exception("Have you installed the dev-requirements? "
"Failed importing {}".format(error)) from error
faker = FakerFactory.create('nl_NL')
pizza_name_faker = FakerFactory.create('it_IT')
current_tz = timezone.get_current_timezone()
_faker = FakerFactory.create('nl_NL')
_pizza_name_faker = FakerFactory.create('it_IT')
_current_tz = timezone.get_current_timezone()
def generate_title():
words = faker.words(random.randint(1, 3))
def _generate_title():
words = _faker.words(random.randint(1, 3))
return ' '.join([word.capitalize() for word in words])
class ProfileFactory(factory.Factory):
class Meta:
class _ProfileFactory(factory.Factory):
class Meta: # pylint: disable=missing-docstring
model = Profile
programme = random.choice(['computingscience', 'informationscience'])
student_number = factory.LazyAttribute(
lambda x: faker.numerify(text="s#######"))
lambda x: _faker.numerify(text="s#######"))
starting_year = factory.LazyAttribute(
lambda x: random.randint(1990, date.today().year))
address_street = factory.LazyAttribute(lambda x: faker.street_address())
address_postal_code = factory.LazyAttribute(lambda x: faker.postcode())
address_city = factory.LazyAttribute(lambda x: faker.city())
address_street = factory.LazyAttribute(lambda x: _faker.street_address())
address_postal_code = factory.LazyAttribute(lambda x: _faker.postcode())
address_city = factory.LazyAttribute(lambda x: _faker.city())
phone_number = '+31' + faker.numerify(text="##########")
phone_number = '+31' + _faker.numerify(text="##########")
class Command(BaseCommand):
"""Command to create fake data to populate the site"""
help = "Creates fake data to test the site with"
def add_arguments(self, parser):
"""
Adds arguments to the argument parser.
:param parser: the argument parser
"""
parser.add_argument(
"-b",
"--board",
......@@ -87,12 +103,18 @@ class Command(BaseCommand):
help="The amount of fake vacancies to add")
def create_board(self, lecture_year, members):
"""
Create a new board
:param int lecture_year: the lecture year this board was active
:param members: the members to add to the board
"""
board = Board()
board.name_nl = "Bestuur {}-{}".format(lecture_year, lecture_year+1)
board.name_en = "Board {}-{}".format(lecture_year, lecture_year+1)
board.description_nl = faker.paragraph()
board.description_en = faker.paragraph()
board.name_nl = "Bestuur {}-{}".format(lecture_year, lecture_year + 1)
board.name_en = "Board {}-{}".format(lecture_year, lecture_year + 1)
board.description_nl = _faker.paragraph()
board.description_en = _faker.paragraph()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
......@@ -103,9 +125,9 @@ class Command(BaseCommand):
board.photo.save(board.name_nl + '.jpeg', ContentFile(icon))
board.since = date(year=lecture_year, month=9, day=1)
board.until = date(year=lecture_year+1, month=8, day=31)
board.until = date(year=lecture_year + 1, month=8, day=31)
board.active = True
board.contact_email = faker.email()
board.contact_email = _faker.email()
board.save()
......@@ -121,12 +143,17 @@ class Command(BaseCommand):
chair.save()
def create_committee(self, members):
"""
Create a committee
:param members: the committee members
"""
committee = Committee()
committee.name_nl = generate_title()
committee.name_nl = _generate_title()
committee.name_en = committee.name_nl
committee.description_nl = faker.paragraph()
committee.description_en = faker.paragraph()
committee.description_nl = _faker.paragraph()
committee.description_en = _faker.paragraph()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
......@@ -136,17 +163,17 @@ class Command(BaseCommand):
) # 620x620 pixels, with 10 pixels padding on each side
committee.photo.save(committee.name_nl + '.jpeg', ContentFile(icon))
committee.since = faker.date_time_between("-10y", "+30d")
committee.since = _faker.date_time_between("-10y", "+30d")
if random.random() < 0.1:
now = date.today()
month = timedelta(days=30)
committee.until = faker.date_time_between_dates(committee.since,
now + 2 *
month).date()
committee.until = _faker.date_time_between_dates(committee.since,
now + 2 *
month).date()
committee.active = random.random() < 0.9
committee.contact_email = faker.email()
committee.contact_email = _faker.email()
committee.save()
......@@ -162,6 +189,12 @@ class Command(BaseCommand):
chair.save()
def create_committee_membership(self, member, committee):
"""
Create committee membership
:param member: the member to add to the committee
:param committee: the committee to add the member to
"""
membership = CommitteeMembership()
membership.member = member
......@@ -169,23 +202,28 @@ class Command(BaseCommand):
today = date.today()
month = timedelta(days=30)
membership.since = faker.date_time_between_dates(committee.since,
today + month).date()
membership.since = _faker.date_time_between_dates(committee.since,
today + month).date()
if random.random() < 0.2 and membership.since < today:
membership.until = faker.date_time_between_dates(membership.since,
today).date()
membership.until = _faker.date_time_between_dates(membership.since,
today).date()
membership.save()
def create_event(self, committees):
"""
Create an event
:param committees: the committees to pick the organiser from
"""
event = Event()
event.title_nl = generate_title()
event.title_nl = _generate_title()
event.title_en = event.title_nl
event.description_nl = faker.paragraph()
event.description_en = faker.paragraph()
event.start = faker.date_time_between("-1y", "+3m", current_tz)
event.description_nl = _faker.paragraph()
event.description_en = _faker.paragraph()
event.start = _faker.date_time_between("-1y", "+3m", _current_tz)
duration = math.ceil(random.expovariate(0.2))
event.end = event.start + timedelta(hours=duration)
event.organiser = random.choice(committees)
......@@ -193,29 +231,29 @@ class Command(BaseCommand):
if random.random() < 0.5:
week = timedelta(days=7)
event.registration_start = faker.date_time_between_dates(
datetime_start=event.start - 4*week,
datetime_end=event.start - week,
tzinfo=current_tz)
event.registration_end = faker.date_time_between_dates(
datetime_start=event.registration_start,
datetime_end=event.start,
tzinfo=current_tz)
event.cancel_deadline = faker.date_time_between_dates(
datetime_start=event.registration_end,
datetime_end=event.start,
tzinfo=current_tz)
event.location_nl = faker.street_address()
event.registration_start = _faker.date_time_between_dates(
datetime_start=event.start - 4 * week,
datetime_end=event.start - week,
tzinfo=_current_tz)
event.registration_end = _faker.date_time_between_dates(
datetime_start=event.registration_start,
datetime_end=event.start,
tzinfo=_current_tz)
event.cancel_deadline = _faker.date_time_between_dates(
datetime_start=event.registration_end,
datetime_end=event.start,
tzinfo=_current_tz)
event.location_nl = _faker.street_address()
event.location_en = event.location_nl
event.map_location = event.location_nl
if random.random() < 0.5:
event.price = random.randint(100, 2500) / 100
event.fine = max(
5.0,
random.randint(round(100 * event.price),
round(500 * event.price)) / 100)
5.0,
random.randint(round(100 * event.price),
round(500 * event.price)) / 100)
if random.random() < 0.5:
event.max_participants = random.randint(20, 200)
......@@ -225,12 +263,13 @@ class Command(BaseCommand):
event.save()
def create_partner(self):
"""Create a new random partner"""
partner = Partner()
partner.is_active = random.random() < 0.75
partner.name = faker.company() + ' ' + faker.company_suffix()
partner.slug = faker.slug()
partner.link = faker.uri()
partner.name = _faker.company() + ' ' + _faker.company_suffix()
partner.slug = _faker.slug()
partner.link = _faker.uri()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
......@@ -240,25 +279,27 @@ class Command(BaseCommand):
) # 620x620 pixels, with 10 pixels padding on each side
partner.logo.save(partner.name + '.jpeg', ContentFile(icon))
partner.address = faker.street_address()
partner.zip_code = faker.postcode()
partner.city = faker.city()
partner.address = _faker.street_address()
partner.zip_code = _faker.postcode()
partner.city = _faker.city()
partner.save()
def create_pizza(self, prod_type):
"""Create a new random pizza product"""
product = Product()
product.name = prod_type + ' ' + pizza_name_faker.last_name()
product.description_nl = faker.sentence()
product.description_nl = faker.sentence()
product.name = prod_type + ' ' + _pizza_name_faker.last_name()
product.description_nl = _faker.sentence()
product.description_nl = _faker.sentence()
product.price = random.randint(250, 1000) / 100
product.available = random.random() < 0.9
product.save()
def create_user(self):
fakeprofile = faker.profile()
"""Create a new random user"""
fakeprofile = _faker.profile()
fakeprofile['password'] = ''.join(
random.choice(string.ascii_uppercase + string.digits)
for _ in range(16))
......@@ -268,7 +309,7 @@ class Command(BaseCommand):
user.first_name = fakeprofile['name'].split()[0]
user.last_name = ' '.join(fakeprofile['name'].split()[1:])
profile = ProfileFactory()
profile = _ProfileFactory()
profile.user_id = user.id
profile.birthday = fakeprofile['birthdate']
profile.website = fakeprofile['website'][0]
......@@ -284,9 +325,9 @@ class Command(BaseCommand):
membership = Membership()
membership.user_id = user.id
membership.since = faker.date_time_between(
membership.since = _faker.date_time_between(
start_date='-4y', end_date='now', tzinfo=None)
membership.until = random.choice([faker.date_time_between(
membership.until = random.choice([_faker.date_time_between(
start_date='-2y', end_date='+2y', tzinfo=None), None])
membership.type = random.choice(
['member', 'supporter', 'honorary'])
......@@ -296,15 +337,21 @@ class Command(BaseCommand):
membership.save()
def create_vacancy(self, partners, categories):
"""
Create a new random vacancy
:param partners: the partners to choose a partner from
:param categories: the categories to choose this vacancy from
"""
vacancy = Vacancy()
vacancy.title = faker.job()
vacancy.description = faker.paragraph()
vacancy.link = faker.uri()
vacancy.title = _faker.job()
vacancy.description = _faker.paragraph()
vacancy.link = _faker.uri()
vacancy.partner = random.choice(partners)
if random.random() < 0.5:
vacancy.expiration_date = faker.date_time_between("-1y", "+1y")
vacancy.expiration_date = _faker.date_time_between("-1y", "+1y")
vacancy.save()
......@@ -312,15 +359,21 @@ class Command(BaseCommand):
random.randint(0, 3))
def create_vacancy_category(self):
"""Create new random vacancy categories"""
category = VacancyCategory()
category.name_nl = faker.text(max_nb_chars=30)
category.name_en = faker.text(max_nb_chars=30)
category.slug = faker.slug()
category.name_nl = _faker.text(max_nb_chars=30)
category.name_en = _faker.text(max_nb_chars=30)
category.slug = _faker.slug()
category.save()
def handle(self, **options):
def handle(self, *args, **options): # pylint: disable=too-many-branches
"""
Handle the command being executed
:param options: the passed-in options
"""
opts = ['board', 'committee', 'event', 'partner', 'pizza', 'user',
'vacancy']
......
"""Provides various utilities that are useful across the project"""
from django.utils import timezone
def datetime_to_lectureyear(date):
"""Convert a date to the start of the lectureyear
"""Convert a :class:`~datetime.date` to the start of the lectureyear
>>> from datetime import date, datetime, timezone
>>> nov_23 = date(1990, 11, 7)
......@@ -12,7 +13,7 @@ def datetime_to_lectureyear(date):
>>> datetime_to_lectureyear(mar_2)
1992
Also works on ``datetimes``, but they need to be tz-aware:
Also works on :class:`~datetime.datetime`, but they need to be tz-aware:
>>> new_year = datetime(2000, 1, 1, tzinfo=timezone.utc)
>>> datetime_to_lectureyear(new_year)
......
"""Thumbnail template tags"""
import os
from django import template
......@@ -6,11 +7,19 @@ from django.db.models.fields.files import ImageFieldFile
from django.urls import reverse
from django.utils.http import urlquote
register = template.Library()
register = template.Library() # pylint: disable=invalid-name
@register.simple_tag
def thumbnail(path, size, fit=True):
"""
Get the thumbnail path for the specified image path.
:param path: the path or image file to generate the thumb for
:type path: ImageFieldFile or str
:return: the path to the associated thumbnail
:rtype: str
"""
if isinstance(path, ImageFieldFile):
path = path.name
......
"""Tests for the ``utils`` module"""
# pylint: disable=attribute-defined-outside-init
import doctest
from django.core.exceptions import FieldError
......@@ -15,7 +17,7 @@ LANGUAGES = [
]
def load_tests(loader, tests, ignore):
def load_tests(_loader, tests, _ignore):
"""
Load all tests in this module
"""
......@@ -28,95 +30,116 @@ def load_tests(loader, tests, ignore):
@override_settings(LANGUAGES=LANGUAGES)
class TestTranslateMeta(TestCase):
"""Test the translate metaclass"""
def test_translate_adds_fields(self):
class TestItem(models.Model, metaclass=ModelTranslateMeta):
"""Confirm that we get extra items added to the class"""
class _TestItem(models.Model, metaclass=ModelTranslateMeta):
text = MultilingualField(models.TextField)
self.assertTrue(hasattr(TestItem, 'text_en'))
self.assertTrue(hasattr(TestItem, 'text_nl'))
self.assertTrue(hasattr(TestItem, 'text_fr'))
self.assertTrue(hasattr(TestItem, 'text'))
def test_verbose_name_kwargs(self):
class TestItem2(models.Model, metaclass=ModelTranslateMeta):
self.assertTrue(hasattr(_TestItem, 'text_en'),
"expected text_en field")
self.assertTrue(hasattr(_TestItem, 'text_nl'),
"expected text_nl field")
self.assertTrue(hasattr(_TestItem, 'text_fr'),
"expected text_fr field")
self.assertTrue(hasattr(_TestItem, 'text'),
"expect text as placeholder")
def test_verbose_name(self):
"""
Confirm that passing verbose_name as kwargs or args works.
"""
class _TestItem2(models.Model, metaclass=ModelTranslateMeta):
text = MultilingualField(models.TextField, verbose_name='Text')
<