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

Merge branch 'add-automatic-model-testing' into 'master'

Add automatic testing for model str method

See merge request !1186
parents d76512f2 84a0bcd2
...@@ -351,10 +351,11 @@ class MemberGroupMembership(models.Model, metaclass=ModelTranslateMeta): ...@@ -351,10 +351,11 @@ class MemberGroupMembership(models.Model, metaclass=ModelTranslateMeta):
self.member.save() self.member.save()
def __str__(self): def __str__(self):
return "{} membership of {} since {}, until {}".format(self.member, return _("{member} membership of {group} "
self.group, "since {since}, until {until}").format(member=self.member,
self.since, group=self.group,
self.until) since=self.since,
until=self.until)
class Meta: class Meta:
verbose_name = _('group membership') verbose_name = _('group membership')
......
...@@ -164,3 +164,6 @@ class Slide(models.Model, metaclass=ModelTranslateMeta): ...@@ -164,3 +164,6 @@ class Slide(models.Model, metaclass=ModelTranslateMeta):
"""Is this slide currently visible""" """Is this slide currently visible"""
return ((self.until is None or self.until > timezone.now()) and return ((self.until is None or self.until > timezone.now()) and
(self.since is None or self.since <= timezone.now())) (self.since is None or self.since <= timezone.now()))
def __str__(self):
return self.title
...@@ -72,6 +72,9 @@ class Newsletter(models.Model, metaclass=ModelTranslateMeta): ...@@ -72,6 +72,9 @@ class Newsletter(models.Model, metaclass=ModelTranslateMeta):
("send_newsletter", "Can send newsletter"), ("send_newsletter", "Can send newsletter"),
) )
def __str__(self):
return self.title
class NewsletterContent(models.Model, metaclass=ModelTranslateMeta): class NewsletterContent(models.Model, metaclass=ModelTranslateMeta):
"""Describes one piece of basic content of a newsletter""" """Describes one piece of basic content of a newsletter"""
...@@ -115,6 +118,9 @@ class NewsletterContent(models.Model, metaclass=ModelTranslateMeta): ...@@ -115,6 +118,9 @@ class NewsletterContent(models.Model, metaclass=ModelTranslateMeta):
class Meta: class Meta:
order_with_respect_to = 'newsletter' order_with_respect_to = 'newsletter'
def __str__(self):
return self.title
class NewsletterItem(NewsletterContent): class NewsletterItem(NewsletterContent):
"""Describes one piece of text content of a newsletter""" """Describes one piece of text content of a newsletter"""
......
...@@ -7,8 +7,8 @@ msgid "" ...@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 21:27+0100\n" "POT-Creation-Date: 2019-03-02 16:50+0100\n"
"PO-Revision-Date: 2019-02-20 21:27+0100\n" "PO-Revision-Date: 2019-03-02 16:51+0100\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n" "Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: nl\n" "Language: nl\n"
...@@ -133,6 +133,11 @@ msgstr "betalingen" ...@@ -133,6 +133,11 @@ msgstr "betalingen"
msgid "Process payments" msgid "Process payments"
msgstr "Verwerk betalingen" msgstr "Verwerk betalingen"
#: models.py
#, python-brace-format
msgid "Payment of {amount}"
msgstr "Betaling van {amount}"
#: templates/admin/payments/change_form.html templates/payments/widget.html #: templates/admin/payments/change_form.html templates/payments/widget.html
msgid "Process (cash payment)" msgid "Process (cash payment)"
msgstr "Verwerk (contant)" msgstr "Verwerk (contant)"
......
...@@ -90,3 +90,6 @@ class Payment(models.Model): ...@@ -90,3 +90,6 @@ class Payment(models.Model):
permissions = ( permissions = (
('process_payments', _("Process payments")), ('process_payments', _("Process payments")),
) )
def __str__(self):
return _("Payment of {amount}").format(amount=self.amount)
...@@ -7,8 +7,8 @@ msgid "" ...@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-05 15:13+0200\n" "POT-Creation-Date: 2019-03-02 16:51+0100\n"
"PO-Revision-Date: 2018-03-21 20:03+0100\n" "PO-Revision-Date: 2019-03-02 16:51+0100\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n" "Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: nl\n" "Language: nl\n"
...@@ -16,7 +16,7 @@ msgstr "" ...@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.6\n" "X-Generator: Poedit 2.2.1\n"
#: admin.py #: admin.py
#, python-brace-format #, python-brace-format
...@@ -27,8 +27,7 @@ msgstr "<strong><a href=\"{link}\">Bestellingen</a></strong>" ...@@ -27,8 +27,7 @@ msgstr "<strong><a href=\"{link}\">Bestellingen</a></strong>"
msgid "Either specify a member or a name" msgid "Either specify a member or a name"
msgstr "Specificeer een lid of vul een naam in" msgstr "Specificeer een lid of vul een naam in"
#: apps.py templates/pizzas/add_order.html templates/pizzas/index.html #: apps.py
#: templates/pizzas/orders.html templates/pizzas/overview.html
msgid "Pizzas" msgid "Pizzas"
msgstr "Pizza's" msgstr "Pizza's"
...@@ -40,6 +39,10 @@ msgstr "Bestel vanaf" ...@@ -40,6 +39,10 @@ msgstr "Bestel vanaf"
msgid "Order until" msgid "Order until"
msgstr "Bestel tot" msgstr "Bestel tot"
#: models.py
msgid "Send an order notification"
msgstr "Stuur een bestellingsherinnering"
#: models.py #: models.py
msgid "This event cannot overlap with {}." msgid "This event cannot overlap with {}."
msgstr "Dit evenement kan niet overlappen met {}." msgstr "Dit evenement kan niet overlappen met {}."
...@@ -68,10 +71,20 @@ msgstr "Bestel beperkte producten" ...@@ -68,10 +71,20 @@ msgstr "Bestel beperkte producten"
msgid "Use this for non-members" msgid "Use this for non-members"
msgstr "Vul dit in voor niet-leden" msgstr "Vul dit in voor niet-leden"
#: models.py
#, python-brace-format
msgid "Order by {member_name}: {product}"
msgstr "Bestelling van {member_name}: {product}"
#: templates/pizzas/add_order.html #: templates/pizzas/add_order.html
msgid "Add order" msgid "Add order"
msgstr "Nieuwe bestelling toevoegen" msgstr "Nieuwe bestelling toevoegen"
#: templates/pizzas/add_order.html templates/pizzas/index.html
#: templates/pizzas/orders.html templates/pizzas/overview.html
msgid "pizzas"
msgstr "pizza's"
#: templates/pizzas/add_order.html #: templates/pizzas/add_order.html
#, python-format #, python-format
msgid "Add order for %(title)s" msgid "Add order for %(title)s"
...@@ -109,7 +122,7 @@ msgstr "Bestel eten voor %(title)s" ...@@ -109,7 +122,7 @@ msgstr "Bestel eten voor %(title)s"
msgid "There is no current event for which you can order food" msgid "There is no current event for which you can order food"
msgstr "Er is nu geen evenement waar je eten voor kunt bestellen" msgstr "Er is nu geen evenement waar je eten voor kunt bestellen"
#: templates/pizzas/index.html #: templates/pizzas/index.html templates/pizzas/orders.html
msgid "All products" msgid "All products"
msgstr "Alle producten" msgstr "Alle producten"
...@@ -194,10 +207,6 @@ msgstr "Weet je zeker dat je de bestelling aan wilt passen?" ...@@ -194,10 +207,6 @@ msgstr "Weet je zeker dat je de bestelling aan wilt passen?"
msgid "Orders for %(title)s" msgid "Orders for %(title)s"
msgstr "Bestellingen voor %(title)s" msgstr "Bestellingen voor %(title)s"
#: templates/pizzas/orders.html
msgid "Add a new order"
msgstr "Nieuwe bestelling toevoegen"
#: templates/pizzas/orders.html #: templates/pizzas/orders.html
msgid "Overview" msgid "Overview"
msgstr "Overzicht" msgstr "Overzicht"
...@@ -206,11 +215,11 @@ msgstr "Overzicht" ...@@ -206,11 +215,11 @@ msgstr "Overzicht"
msgid "Paid" msgid "Paid"
msgstr "Betaald" msgstr "Betaald"
#: templates/pizzas/orders.html templates/pizzas/overview.html #: templates/pizzas/orders.html
msgid "Yes" msgid "Yes"
msgstr "Ja" msgstr "Ja"
#: templates/pizzas/orders.html templates/pizzas/overview.html #: templates/pizzas/orders.html
msgid "No" msgid "No"
msgstr "Nee" msgstr "Nee"
......
...@@ -209,3 +209,9 @@ class Order(models.Model): ...@@ -209,3 +209,9 @@ class Order(models.Model):
class Meta: class Meta:
unique_together = ('pizza_event', 'member',) unique_together = ('pizza_event', 'member',)
def __str__(self):
return _("Order by {member_name}: {product}").format(
member_name=self.member_name,
product=self.product
)
...@@ -2,8 +2,8 @@ msgid "" ...@@ -2,8 +2,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-19 15:30+0200\n" "POT-Creation-Date: 2019-03-02 16:51+0100\n"
"PO-Revision-Date: 2018-08-19 15:30+0200\n" "PO-Revision-Date: 2019-03-02 16:52+0100\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n" "Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: nl\n" "Language: nl\n"
...@@ -11,7 +11,7 @@ msgstr "" ...@@ -11,7 +11,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.1.1\n" "X-Generator: Poedit 2.2.1\n"
#: admin.py #: admin.py
msgid "Enable selected devices" msgid "Enable selected devices"
...@@ -33,6 +33,10 @@ msgstr "Pushnotificaties" ...@@ -33,6 +33,10 @@ msgstr "Pushnotificaties"
msgid "name" msgid "name"
msgstr "naam" msgstr "naam"
#: models.py
msgid "description"
msgstr "beschrijving"
#: models.py #: models.py
msgid "registration token" msgid "registration token"
msgstr "registratie token" msgstr "registratie token"
...@@ -54,32 +58,9 @@ msgid "language" ...@@ -54,32 +58,9 @@ msgid "language"
msgstr "taal" msgstr "taal"
#: models.py #: models.py
msgid "General" #, python-brace-format
msgstr "Algemeen" msgid "{user}s {device_type} device"
msgstr "{user}s {device_type} apparaat"
#: models.py
msgid "Pizza"
msgstr "Pizza"
#: models.py
msgid "Events"
msgstr "Evenementen"
#: models.py
msgid "Newsletter"
msgstr "Nieuwsbrief"
#: models.py
msgid "Messages from partners"
msgstr "Berichten van partners"
#: models.py
msgid "Photos"
msgstr "Foto's"
#: models.py
msgid "Board"
msgstr "Bestuur"
#: models.py #: models.py
msgid "title" msgid "title"
...@@ -89,6 +70,10 @@ msgstr "titel" ...@@ -89,6 +70,10 @@ msgstr "titel"
msgid "body" msgid "body"
msgstr "bericht" msgstr "bericht"
#: models.py
msgid "url"
msgstr "url"
#: models.py #: models.py
msgid "category" msgid "category"
msgstr "categorie" msgstr "categorie"
......
...@@ -71,6 +71,11 @@ class Device(models.Model): ...@@ -71,6 +71,11 @@ class Device(models.Model):
class Meta: class Meta:
unique_together = ('registration_id', 'user',) unique_together = ('registration_id', 'user',)
def __str__(self):
return _(
"{user}s {device_type} device"
).format(user=self.user, device_type=self.type)
class MessageManager(models.Manager): class MessageManager(models.Manager):
"""Returns manual messages only""" """Returns manual messages only"""
......
...@@ -107,7 +107,7 @@ class Entry(models.Model): ...@@ -107,7 +107,7 @@ class Entry(models.Model):
return self.renewal.__str__() return self.renewal.__str__()
except Renewal.DoesNotExist: except Renewal.DoesNotExist:
pass pass
return super().__str__() return _("Registration entry")
class Meta: class Meta:
verbose_name = _('entry') verbose_name = _('entry')
......
from unittest import mock
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core import mail from django.core import mail
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
...@@ -42,8 +40,7 @@ class EntryTest(TestCase): ...@@ -42,8 +40,7 @@ class EntryTest(TestCase):
membership_type=Membership.MEMBER, membership_type=Membership.MEMBER,
) )
@mock.patch('django.db.models.Model.__str__') def test_str(self):
def test_str(self, str_mock):
entry = Entry(registration=self.registration) entry = Entry(registration=self.registration)
self.assertEqual(str(entry), '{} {} ({})'.format( self.assertEqual(str(entry), '{} {} ({})'.format(
self.registration.first_name, self.registration.last_name, self.registration.first_name, self.registration.last_name,
...@@ -54,11 +51,6 @@ class EntryTest(TestCase): ...@@ -54,11 +51,6 @@ class EntryTest(TestCase):
self.member.first_name, self.member.last_name, self.member.first_name, self.member.last_name,
self.member.email)) self.member.email))
str_mock.return_value = 'return str'
entry = Entry()
str(entry)
str_mock.assert_called_once_with()
class RegistrationTest(TestCase): class RegistrationTest(TestCase):
"""Tests registrations""" """Tests registrations"""
......
from django.apps import apps
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.test import TestCase
def create_models_test_class(classname):
"""
Create the class for all the model __str__ tests.
This class is created dynamically with the type(name, bases, dict)
function, it includes test functions to test all the models in the project.
:param classname: The name to use for the created class
:return: An instance of the TestCase class with generated tests
"""
def create_model_test_function(name, test_model):
"""Create a test function that tests database model test_model."""
def str_function_is_overwritten_for(self):
"""Check if the test_model overrides __str__ by comparing the
implementation to the super class version."""
instance = test_model()
try:
# the implemented __str__ method should be different from the
# __str__ function in the parent class (Model)
self.assertNotEqual(
str(instance),
models.Model.__str__(instance)
)
except (ObjectDoesNotExist, AttributeError, KeyError, TypeError):
# if the __str__ method relies on any fields which were not
# instantiated, it throws a derivative of ObjectDoesNotExist,
# or one of the other errors which means it is different from
# the parent class implementation
pass
# the testing framework uses qualname to print the method name and
# its class
str_function_is_overwritten_for.__qualname__ = f'{classname}.{name}'
str_function_is_overwritten_for.__name__ = name
return str_function_is_overwritten_for
tests = dict()
# django keeps track of the models it knows of, and we can request that
# here by default these are only the models implemented by the project
for model in apps.get_models():
funcname = f'test_str_method_overwritten_for_{model.__name__}'
tests[funcname] = create_model_test_function(funcname, model)
# type() is the class constructor, it's arguments are
# name: name of the class
# bases: classes the new class inherits from
# dict: attributes (which includes methods) of this class
return type(classname, (TestCase,), tests)
# create the class to be picked up by the django test runner
ModelsTest = create_models_test_class('ModelsTest')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment