Skip to content
deby edited this page Jan 20, 2023 · 29 revisions

↑ Parent: MagiModel utils

← Previous: Customize views with MagiModel properties

Events are a common collection for MagiCircles websites, especially those about mobile games.

MagiCircles provides a bunch of abstract models and collections you can use to save some time and effort.

Models

BaseEvent model

${PROJECT}/models.py:

from magi.abstract_models import BaseEvent

class Event(BaseEvent):
    ...

It contains:

  • a name (translatable)
  • a description (allowing markdown)
  • a start date
  • an end date
  • an image

event.status can be access to know the status of the event. For details of what status can be, see getEventStatus in Python Utils.

The following variables can be set in your Event model to customize it:

  • STATUS_STARTS_WITHIN: Number of days. By default, event.status returns "starts_soon" 3 days before the start date. You can set the value to 0 to never get "starts_soon".
  • STATUS_ENDS_WITHIN: Number of days. By default, event.status returns "ended_recently" 3 days after the end date. You can set the value to 0 to never get "ended_recently".

Base event model when multiple versions exist

from collections import OrderedDict

VERSIONS = OrderedDict((
    ('JP', {
        'translation': _('Japanese version'),
        'prefix': 'jp_',
    }),
    ('WW', {
        'translation': _('Worldwide version'),
        'prefix': 'ww_',
    }),
))

VERSIONS is an ordered dict with values being dicts containing:

Key Value Example Default
translation Full version name _('Japanese version') required
prefix Prefix used for each field per version jp_ required
language Language in which this version is. You can also set languages (list) if there are multiple languages. ja All languages
image Used in forms and filters when selecting a version language/ja None
icon Used in forms and filters when selecting a version jp None
timezone In which timezone should dates be displayed for this version? (In addition to local time) You can also set timezones (list) if you want to display multiple timezones. Asia/Tokyo Only local time gets displayed

Here's a more complete example:

VERSIONS = OrderedDict((
    ('JP', {
        'translation': _('Japanese version'),
        'prefix': 'jp_',
        'language': 'ja',
        'image': 'language/ja',
        'icon': 'jp',
        'timezone': 'Asia/Tokyo',
    }),
    ('WW', {
        'translation': _('Worldwide version'),
        'prefix': 'ww_',
        'languages': [ 'en', 'fr' ],
        'image': 'language/world',
        'icon': 'world',
        'timezone': 'UTC',
    }),
))

To create an Event model using VERSIONS, you'll need to call getBaseEventWithVersions:

${PROJECT}/models.py:

from magi.abstract_models import getBaseEventWithVersions

class Event(getBaseEventWithVersions(VERSIONS)):
    ...

Just like BaseEvent, your Event model will contain:

  • a name (translatable)
  • a description (allowing markdown)

It also contains, for each version, using the prefix specified in VERSIONS:

  • a start date (Example: ww_start_date, jp_start_date)
  • an end date (Example: ww_end_date, jp_end_date)

If languages are specified for each version, it will contain, for each language for each version:

  • an image (Example: ww_en_image, ww_th_image, jp_ja_image)

Otherwise, it will contain for each version:

  • an image (Example: ww_image, jp_image)

getBaseEventWithVersions allows a few optional parameters:

Name Description Type
extra_fields Allows to add custom model fields per version.
  • key must contain '{}' which will be replaced with the version prefix
  • value must be a lambda that takes version name and version details (dict) and returns the model field (ex: lambda _vn, _v: models.CharField(...))
ordered dict of field name -> lambda
extra_fields_per_language Allows to add custom model fields per language.
  • key must contain '{}' which will be replaced with the version prefix followed by language prefix. Ex: ww_en_help_text_image
  • value must be a lambda that takes version name, version details (dict) and language, and returns the model field (ex: lambda _vn, _v, _l: models.CharField(...))
ordered dict of field name -> lambda
extra_utils Allows to specify your own utils per version, being properties, methods, or variables.
  • key must contain '{}' which will be replaced with the version prefix
  • value must be a lambda that takes version name and version details (dict) and returns your utility.
Ex: { '{}duration': lambda _version_name, _version: property(lambda _s: _s.getDuration(_version_name)) }
ordered dict of field name -> lambda
fallbacks See Properties per version dict of field name -> bool
Ex: { '{}image': False }
defaults See Utils per version and Properties per version dict of field name -> default value
Ex: { '{}image': 'default.png' }

In this example, the list of cards given during an event varies depending on which version, so we want to have a cards field per version. We also have a "banner" that varies depending on which version AND language. We also want an utility property to check if the version has a start date.

${PROJECT}/models.py:

from django.utils.translation import ugettext_lazy as _, string_concat
from magi.utils	import getVerboseLanguage, uploadItem
from magi.versions_utils import	getFieldNameForVersion

EVENT_EXTRA_FIELDS = OrderedDict([
    ('{}cards', lambda _version_name, _version: models.ManyToManyField(
        Card, related_name=getFieldNameForVersion(u'{}events', _version), null=True,
        verbose_name=string_concat(_version['translation'], ' - ', _('Cards')),
    )),
])

EVENT_EXTRA_FIELDS_PER_LANGUAGE = OrderedDict([
    ('{}banner', lambda _version_name, _version, _language: models.ImageField(
        string_concat(_version['translation'], ' - ', _('Banner'),' - ', getVerboseLanguage(_language)),
	upload_to=uploadItem('events/banners/{}/{}'.format(_version_name, _language)), null=True,
    )),
])

EVENT_EXTRA_UTILS = OrderedDict([
    (u'{}has_start_date', lambda _version_name, _version: property(lambda _s: bool(_s.get_field_for_version('{}start_date', _version_name))),

class Event(getBaseEventWithVersions(
	VERSIONS,
	extra_fields=EVENT_EXTRA_FIELDS,
        extra_fields_per_language=EVENT_EXTRA_FIELDS_PER_LANGUAGE,
        extra_utils=EVENT_EXTRA_UTILS,
)):
    ...

⚠️ Fields per version and language are generally recommended for images, but not for texts. For texts, you can simply use a regular translated field.

Your Event model will also provide a few utility methods and properties for you:

Properties per version

All the default fields and custom fields will be available per version using their prefix as native model fields (extra_fields). Ex: ww_start_date.

The following utility properties are also available:

Name Example Description
{}name ww_name When language or languages is specified in VERSIONS, will return the name of the event in the given version. When a version has multiple languages, it will automatically return the name in the most relevant language, based on the current user's language
{}status ww_status The status of the event in given version, see getEventStatus in Python Utils
Properties with auto-detected version

All the fields with prefixes are also accessible without prefixes: when ww_start_date and jp_start_date exist, accessing relevant_start_date will give you the most relevant start date based on what's more relevant for the current user.

These properties will automatically be available for all the fields, including the default ones and your custom ones (extra_fields). These properties will automatically determine the relevant value to return within the available versions.

All the fields per language per version will also have a shortcut property. For example, if Worldwide version is available in English and Thai, you'll be able to access the images with ww_en_image and ww_th_image, but you'll also be able to access ww_image: it will simply automatically determine which image to give you based on which version is more relevant to the current user.

event = models.Event.objects.get(pk=1)
print event.versions # [ "JP", "WW" ]
print event.relevant_versions # [ "WW" ]
print django.utils.translation.get_language() # "en"
event.image # will return value of ww_en_image

By default, if none of the "relevant" versions have a value, it will try to fallback to other versions that have a value.

This can be disabled by setting fallbacks to False (as a parameter when calling getBaseEventWithVersions) for the given field name. Finally, if no value is available in any version (or fallback is disabled), it will return what's in defaults (as a parameter when calling getBaseEventWithVersions) for the given field, or None.

Name Description
relevant_version The most relevant version for this event
relevant_versions The most relevant versions for this event
opened_versions Which versions would be opened by default when viewing the details of an event (item view). Opened versions checks for relevancy without taking into account which version(s) the current user plays or which language the current user prefers.
relevant_name When language or languages is specified in VERSIONS, returns the most relevant name for this event by auto-detecting the most relevant language for the user
versions_display_order List of versions, sorted by recommended display order (opened first, then other relevant versions, then all other versions)
Other properties
Name Description Example
VERSIONS The dict you provided with the details for each version OrderedDict([('JP', { ...}, ])
FIELDS_PER_VERSION List of fields per version ['{}start_date', '{}end_date']
ALL_FIELDS_PER_VERSION Actual fields names per version ['jp_start_date', 'jp_end_date', 'ww_start_date', 'ww_end_date']
FIELDS_PER_VERSION_AND_LANGUAGE List of fields per language per version ['{}image']
ALL_FIELDS_PER_VERSION_AND_LANGUAGE Actual fields names per language per version ['jp_ja_image', 'ww_en_image', 'ww_th_image']
FIELDS_PER_VERSION_AND_LANGUAGE_BY_LANGUAGE Same, but as a dictionary per language {'en': ['ww_en_image'], 'th': ['ww_th_image'], 'ja': ['jp_ja_image']}
ALL_FIELDS_BY_VERSION Actual fields names of all the fields per version, including per language per version, as a dictionary per version { 'JP': ['jp_start_date', 'jp_end_date', 'jp_ja_image'], 'WW': ['ww_start_date', 'ww_end_date', 'ww_en_image', 'ww_th_image'] }
ALL_VERSION_FIELDS_BY_NAME Actual fields names by template field names of all the fields (per version or per version and language), as a dictionary {'{}image': ['jp_ja_image', 'ww_en_image', 'ww_th_image'], '{}start_date': ['jp_start_date', 'ww_start_date']}
Methods per version

get_{}_for_version methods will automatically be available for all the default fields and custom fields (extra_fields). These method take the version name as parameter and return the value for that version. They don't use defaults, but you can call the method and follow it with or your_default_value

event = models.Event.objects.get(pk=1)
event.get_image_for_version('WW')

In addition, the following methods are available per version:

Name Parameters Description
get_name_for_version version_name When language or languages is specified in VERSIONS, returns the most relevant name for this event and this version by auto-detecting the most relevant language for the user
get_field_for_version field_name, version_name, get_value=None1, language=None This is the same method called by the auto methods get_{}_for_version described above.
get_status_for_version version_name This is the same method called by the auto-properties {}_status described above.
get_translated_values_for_version field_name, version_name When language or languages is specified in VERSIONS, return all the values for a translated field in that version. For example, if WW version has languages ['en', 'th'], then it will return the value in English and Thai. This is useful if you have other translated fields than the name that should still be displayed per version.
get_values_per_languages_for_version field_name, version_name, get_value=None1 Returns a dictionary with keys = languages, values = the value for that language and version
get_value_of_relevant_language_for_version field_name, version_name, default=None Returns the value for the most relevant language for a given version.
get_relevant_translated_value_for_version field_name, version_name, fallback=False, default=None For text fields with translations (like "name"), return the translated value for the most relevant language for a given version. This is useful if you have other translated fields than the name that should still be displayed per version.
Methods with auto-detected version
Name Parameters Description
get_relevant_name return_version=False This is the same method called when accessing relevant_name, but allows an optional parameter to return the version name
get_field_for_relevant_version field_name, default=None, get_value=None1</sup, return_version=False, fallback=True Get the value of a field for the most relevant version. It's the same method called by the auto properties relevant_{} described above, but allows more flexibility using parameters.
get_translated_value_for_relevant_version field_name, default=None, return_version=False When language or languages is specified in VERSIONS, get the value of a translated field for the most relevant version by auto-detecting the most relevant language for the user
Class methods

These methods are directly available at the class level. In other words, you don't need an instance of the model to call them.

Name Parameters Description
get_version_name version_name Returns the value of translation specified in VERSIONS for given version
get_version_image version_name Returns the value specified in VERSIONS for given version
get_version_icon version_name Returns the value specified in VERSIONS for given version
get_field_name_for_version field_name, version_name Returns the name of a field for a given version. For ex: models.Event.get_field_name_for_version('{}name', 'WW') will return "ww_name"
get_field_name_for_version_and_language field_name, version_name, language Returns the name of a field for a given version and language. For ex: models.Event.get_field_name_for_version_and_language('{}image', 'WW', 'en') will return "ww_en_image"`.
get_version_languages version_name Returns the list of languages for a given version
get_version_info version_name, field_name, default=None Returns any field specified in VERSIONS. Fail safe.
get_all_versions_field_names field_name Returns all the actual field names for that field name: ['jp_start_date', 'ww_start_date'] (works with fields per version and language too)

BaseEventParticipation model

${PROJECT}/models.py:

from magi.abstract_models import BaseEventParticipation

class EventParticipation(BaseEventParticipation):
    ...

The collection name is eventparticipation.

It contains an account, and you need to add an event foreign key yourself.

class EventParticipation(BaseEventParticipation):
   event = models.ForeignKey(Event, related_name='participations', verbose_name=_('Event'))

Collections

BaseEventCollection

BaseEventCollection only works with a model made from BaseEvent (with or without versions).

${PROJECT}/magicollections.py:

from magi.abstract_collections import BaseEventCollection as _BaseEventCollection

class EventCollection(_BaseEventCollection):
    queryset = models.Event.objects.all()
Update icons and images

ℹ️ For images, the same settings and methods are available! Just replace "icon" with "image".

For BaseEvent models (without versions)
  • Available settings:
Key Value Default Example
base_fields_icons To use instead of fields_icons when setting icons. Make sure you extend the existing value instead of overriding it A bunch of icons already set base_fields_icons = _BaseEventCollection.base_fields_icons.copy()
base_fields_icons.update({ 'boost_value': 'statistics' })
With versions
  • Available settings:
Key Value Default Example
base_fields_icons To use instead of fields_icons when setting icons for fields that are not per version or language. Make sure you extend the existing value instead of overriding it A bunch of icons already set base_fields_icons = _BaseEventCollection.base_fields_icons.copy()
base_fields_icons.update({ 'boost_value': 'statistics' })
fields_icons_per_version Set icons for fields that are per version. Make sure you extend the existing value instead of overriding it A bunch of icons already set fields_icons_per_version = _BaseEventCollection.fields_icons_per_version.copy()
fields_icons_per_version.update({ '{}start_date': 'event' })
fields_icons_per_version_and_language Same but for fields per version and language
  • Available methods (can be overridden):
Name Description Parameters Return value Default
get_fields_icons_per_version Same as fields_icons_per_version but called dynamically version_name, version Dict A bunch of icons already set
get_fields_icons_per_version_and_language Same but for fields per version and language version_name, version, language
Form and filter form

The BaseEventCollection comes with:

  • a form class
  • a filter form class (in list view)

The form class can be overridden like so:

${PROJECT}/forms.py:

from magi.forms import to_EventForm as _to_EventForm

def to_EventForm(cls):
    form_class = _to_EventForm(cls)
    class _EventForm(form_class):
        ...
    return _EventForm

${PROJECT}/magicollections.py:

from . import forms

class EventCollection(_BaseEventCollection):
    ...
    def to_form_class(self):
        self._form_class = forms.to_EventForm(self)

To override the filter form class:

${PROJECT}/forms.py:

from magi.forms import to_EventFilterForm as _to_EventFilterForm

def to_EventFilterForm(cls):
    form_class = _to_EventFilterForm(cls)
    class _EventFilterForm(form_class):
        ...
    return _EventFilterForm

${PROJECT}/magicollections.py:

class EventCollection(_BaseEventCollection):
    class ListView(_BaseEventCollection.ListView):
        def to_filter_form_class(self):
            self._filter_form = forms.to_EventFilterForm(self)
Item view
  • Item views contain the following settings (can be overriden):
Key Value Default Example
fields_order_before Because fields_order gets generated automatically based on relevant versions, you can set the order of all the other fields with this value. ['name', 'c_versions']
fields_exclude_before To use instead of fields_exclude for fields that are not per version or per version and language. Make sure you extend it and not override it. [] fields_exclude_before = _BaseEventCollection.ItemView.fields_exclude_before + ['boost_value']
fields_exclude_per_version Will exclude given fields for all versions [] ['{}start_date']
fields_exclude_per_version_and_language Will exclude given fields for all versions and languages [] ['{}image']

BaseEventParticipationCollection

Assuming you have an EventParticipation model in ${PROJECT}/models.py (see Abstract models), you can link it to your event collection like so:

${PROJECT}/magicollections.py:

class EventCollection(_BaseEventCollection):
    ...
    collectible = models.EventParticipation

A base event participation collection will get setup automatically.


More

Utils

Can be used with or without using the BaseEvent models.

from magi.utils import eventToCountDownField
Name Description Parameters Return value
getRelevantVersion Returns the most relevant version request=None, exclude_versions=[],
Either: (item=None)
Or: (versions=None, versions_statuses=None)
check_filtered_choice=True, check_status=True, check_accounts=True, check_language=True, fallback_to_first=True
The name of the relevant version or None
getRelevantVersions Return the list of most relevant versions, order by relevance Same as getRelevantVersion List
getAllVersionsOrderedByRelevance Returns all the version names with relevant versions first Same as getRelevantVersion List
sortByRelevantVersions Changes the order of a queryset to sort by relevant version(s) queryset, sorted_field_name='{}start_date' and the same parameters as getRelevantVersion Queryset
toCountDownField Returns a countdown field that can be added to the list of fields displayed under an item in list view or item view, for example using get_extra_fields date, field_name=None, verbose_name=None, sentence=None, classes=None, icon=None, image=None dict (see MagiFields)
eventToCountDownField Auto-determines which date to display based on event status and returns a countdown field made using toCountDownField start_date, end_date, field_name=None, verbose_name=None dict (see MagiFields)

Please also note that most methods available for event models also exist as standalone utility functions with a camel case equivalent.

Example:

from versions_utils import getFieldForRelevantVersion

event = models.Event.objects.get(id=1)
event.get_field_for_relevant_version('start_date')
getFieldForRelevantVersion(e, 'start_date') # will return the same thing as above

How are relevant versions determined?

In order of relevancy:

  • The current user is filtering the list of events by a certain version ("See all Worldwide events")
  • The event in this version is currently running ("current")
  • The event in this version starts soon or ended recently
  • The current user plays that version (has an account with that version)
  • The current user prefers a language in which that version exists (ex: Speaks Japanese and jp version is available in Japanese)
  • Fallback to first version specified in VERSIONS list

Please note that if you're calling functions to check which version(s) is/are relevant, some parameters are available to let you bypass some of these criterion.


Footnotes

1: get_value is a lambda that transforms the value for you. It takes as parameters: item, version_name, version.

→ Next: Form utils

I. Introduction

II. Tutorials

  1. Collections
    1. MagiModel
    2. MagiCollection
    3. MagiForm
    4. MagiFiltersForm
    5. MagiFields
  2. Single pages
  3. Configuring the navbar

III. References

IV. Utils

V. Advanced tutorials

VI. More

Clone this wiki locally