class Commodity(AvailableProductMixin, Product): """ This Commodity model inherits from polymorphic Product, and therefore has to be redefined. """ unit_price = MoneyField( _("Unit price"), decimal_places=3, help_text=_("Net price for this product"), ) product_code = models.CharField( _("Product code"), max_length=255, unique=True, ) quantity = models.PositiveIntegerField( _("Quantity"), default=0, validators=[MinValueValidator(0)], help_text=_("Available quantity in stock") ) # controlling the catalog placeholder = PlaceholderField("Commodity Details") show_breadcrumb = True # hard coded to always show the product's breadcrumb default_manager = TranslatableManager() class Meta: verbose_name = _("Commodity") verbose_name_plural = _("Commodities") def get_price(self, request): return self.unit_price
class Tag(TranslatableModel): """ Tag """ active = models.BooleanField(_('Active'), default=True, help_text=bs.ACTIVE_FIELD_HELP_TEXT) date_added = models.DateTimeField(_('Date added'), auto_now_add=True) last_modified = models.DateTimeField(_('Last modified'), auto_now=True) translations = TranslatedFields( name=models.CharField(_('Name'), max_length=255), slug=models.SlugField(_('Slug'), db_index=True), description=models.TextField(_('description'), blank=True), meta={'unique_together': [('slug', 'language_code')]}, ) objects = TranslatableManager() class Meta: db_table = 'blogit_tags' verbose_name = _('Tag') verbose_name_plural = _('Tags') def __str__(self): return self.safe_translation_getter('name', any_language=True) def get_absolute_url(self, language=None): if not language: language = get_current_language() with switch_language(self, language): return reverse( 'blogit_tag_detail', kwargs={'slug': self.safe_translation_getter('slug')})
class SmartCard(Product): # common product fields unit_price = MoneyField( _("Unit price"), decimal_places=3, help_text=_("Net price for this product"), ) # product properties CARD_TYPE = (2 * ('{}{}'.format(s, t), ) for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro ')) card_type = models.CharField( _("Card Type"), choices=CARD_TYPE, max_length=15, ) SPEED = [(str(s), "{} MB/s".format(s)) for s in (4, 20, 30, 40, 48, 80, 95, 280)] speed = models.CharField( _("Transfer Speed"), choices=SPEED, max_length=8, ) product_code = models.CharField( _("Product code"), max_length=255, unique=True, ) storage = models.PositiveIntegerField( _("Storage Capacity"), help_text=_("Storage capacity in GB"), ) multilingual = TranslatedFields(description=HTMLField( verbose_name=_("Description"), configuration='CKEDITOR_SETTINGS_DESCRIPTION', help_text=_( "Full description used in the catalog's detail view of Smart Cards." ), ), ) default_manager = TranslatableManager() class Meta: verbose_name = _("Smart Card") verbose_name_plural = _("Smart Cards") def get_price(self, request): return self.unit_price def get_product_variant(self, extra): """ SmartCards do not have flavors, they are the product. """ return self
class Presenter(TranslatableModel): ''' Person that participates in the event as a guest, ie. a speaker, a performer, a workshop presenter or a host. First and last name are the only required fields. ''' translations = TranslatedFields( name=models.CharField(max_length=255, default=''), occupation=models.CharField(max_length=255, blank=True), short_bio=models.TextField(blank=True, verbose_name='Short bio'), quote=models.CharField(max_length=255, blank=True, verbose_name='Inspirational quote') ) link = models.URLField(blank=True, verbose_name='Website or social media profile') image = VersatileImageField( 'Image', upload_to='presenters/', width_field='image_width', height_field='image_height', null=True, blank=True, ) image_height = models.PositiveIntegerField(editable=False, null=True) image_width = models.PositiveIntegerField(editable=False, null=True) image_alt = VersatileImageField( 'Image 2', upload_to='presenters/', width_field='image_alt_width', height_field='image_alt_height', null=True, blank=True, ) image_alt_height = models.PositiveIntegerField(editable=False, null=True) image_alt_width = models.PositiveIntegerField(editable=False, null=True) is_published = models.BooleanField(_('Published'), default=True) # Managers are an easy way to create custom filters for queries. # # Documentation link: # https://docs.djangoproject.com/en/2.2/topics/db/managers/ objects = TranslatableManager() speakers = PresenterTypeManager(Activity.TALK) performers = PresenterTypeManager(Activity.PERFORMANCE) side_presenters = PresenterTypeManager(Activity.SIDE_EVENT) hosts = PresenterTypeManager(Activity.HOSTING) slug = EnglishAutoSlugField(populate_from=['name'], overwrite=True) def __str__(self): return self.name
class TranslatableModel(TranslatableModelMixin, models.Model): """ Base model class to handle translations. All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`. """ class Meta: abstract = True # change the default manager to the translation manager objects = TranslatableManager()
class Category(MPTTModel, TranslatableModel): """ Category """ active = models.BooleanField(_('Active'), default=True, help_text=bs.ACTIVE_FIELD_HELP_TEXT) date_added = models.DateTimeField(_('Date added'), auto_now_add=True) last_modified = models.DateTimeField(_('Last modified'), auto_now=True) parent = TreeForeignKey( 'self', models.SET_NULL, blank=True, null=True, related_name='children', verbose_name=_('Parent'), ) translations = TranslatedFields( name=models.CharField(_('Name'), max_length=255), slug=models.SlugField(_('Slug'), db_index=True), description=models.TextField(_('description'), blank=True), meta={'unique_together': [('slug', 'language_code')]}, ) objects = TranslatableManager() class Meta: db_table = 'blogit_categories' verbose_name = _('Category') verbose_name_plural = _('Categories') def __str__(self): return self.safe_translation_getter('name', any_language=True) def get_absolute_url(self, language=None): if not language: language = get_current_language() with switch_language(self, language): return reverse('blogit_category_detail', args=[self.get_path()]) def get_path(self): """ Returns ful url path for category. """ path = [] for obj in self.get_ancestors(include_self=True): path.append(obj.safe_translation_getter('slug', '')) return '/'.join(path)
class Commodity(AvailableProductMixin, Product, TranslatableModelMixin): """ This Commodity model inherits from polymorphic Product, and therefore has to be redefined. """ unit_price = MoneyField( _("Unit price"), decimal_places=3, help_text=_("Net price for this product"), ) product_code = models.ForeignKey( ProductList, on_delete=models.CASCADE, ) multilingual = TranslatedFields( description=HTMLField( verbose_name=_("Description"), configuration='CKEDITOR_SETTINGS_DESCRIPTION', blank=True, help_text= _("Full description used in the catalog's detail view of Smart Cards." ), ), caption=HTMLField( verbose_name=_("Caption"), configuration='CKEDITOR_SETTINGS_DESCRIPTION', blank=True, help_text= _("Full description used in the catalog's detail view of Smart Cards." ), ), ) # controlling the catalog placeholder = PlaceholderField("Commodity Details") show_breadcrumb = True # hard coded to always show the product's breadcrumb default_manager = TranslatableManager() class Meta: verbose_name = _("Commodity") verbose_name_plural = _("Commodities") def get_price(self, request): return self.unit_price
class SmartPhoneModel(Product): """ A generic smart phone model, which must be concretized by a model `SmartPhone` - see below. """ BATTERY_TYPES = [ (1, "Lithium Polymer (Li-Poly)"), (2, "Lithium Ion (Li-Ion)"), ] WIFI_CONNECTIVITY = [ (1, "802.11 b/g/n"), ] BLUETOOTH_CONNECTIVITY = [ (1, "Bluetooth 4.0"), (2, "Bluetooth 3.0"), (3, "Bluetooth 2.1"), ] battery_type = models.PositiveSmallIntegerField( _("Battery type"), choices=BATTERY_TYPES, ) battery_capacity = models.PositiveIntegerField( _("Capacity"), help_text=_("Battery capacity in mAh"), ) ram_storage = models.PositiveIntegerField( _("RAM"), help_text=_("RAM storage in MB"), ) wifi_connectivity = models.PositiveIntegerField( _("WiFi"), choices=WIFI_CONNECTIVITY, help_text=_("WiFi Connectivity"), ) bluetooth = models.PositiveIntegerField( _("Bluetooth"), choices=BLUETOOTH_CONNECTIVITY, help_text=_("Bluetooth Connectivity"), ) gps = models.BooleanField( _("GPS"), default=False, help_text=_("GPS integrated"), ) operating_system = models.ForeignKey( OperatingSystem, on_delete=models.CASCADE, verbose_name=_("Operating System"), ) width = models.DecimalField( _("Width"), max_digits=4, decimal_places=1, help_text=_("Width in mm"), ) height = models.DecimalField( _("Height"), max_digits=4, decimal_places=1, help_text=_("Height in mm"), ) weight = models.DecimalField( _("Weight"), max_digits=5, decimal_places=1, help_text=_("Weight in gram"), ) screen_size = models.DecimalField( _("Screen size"), max_digits=4, decimal_places=2, help_text=_("Diagonal screen size in inch"), ) multilingual = TranslatedFields( description=HTMLField( verbose_name=_("Description"), configuration='CKEDITOR_SETTINGS_DESCRIPTION', help_text=_( "Full description used in the catalog's detail view of Smart Phones."), ), ) default_manager = TranslatableManager() class Meta: verbose_name = _("Smart Phone") verbose_name_plural = _("Smart Phones") def get_price(self, request): """ Return the starting price for instances of this smart phone model. """ if not hasattr(self, '_price'): if self.variants.exists(): currency = self.variants.first().unit_price.currency aggr = self.variants.aggregate(models.Min('unit_price')) self._price = MoneyMaker(currency)(aggr['unit_price__min']) else: self._price = Money() return self._price def get_availability(self, request, **kwargs): variant = self.get_product_variant(**kwargs) return variant.get_availability(request) def deduct_from_stock(self, quantity, **kwargs): variant = self.get_product_variant(**kwargs) variant.deduct_from_stock(quantity) def is_in_cart(self, cart, watched=False, **kwargs): try: product_code = kwargs['product_code'] except KeyError: return cart_item_qs = CartItem.objects.filter(cart=cart, product=self) for cart_item in cart_item_qs: if cart_item.product_code == product_code: return cart_item def get_product_variant(self, **kwargs): try: product_code = kwargs.get('product_code') return self.variants.get(product_code=product_code) except SmartPhoneVariant.DoesNotExist as e: raise SmartPhoneModel.DoesNotExist(e)
class KhimageModel(Product): title = models.CharField(_("Title"), max_length=250, null=True) author = models.ForeignKey(Person, null=True, blank=True, verbose_name=_('author')) """ A generic smart phone model, which must be concretized by a model `SmartPhone` - see below. """ #~ BATTERY_TYPES = ( #~ (1, "Lithium Polymer (Li-Poly)"), #~ (2, "Lithium Ion (Li-Ion)"), #~ ) #~ WIFI_CONNECTIVITY = ( #~ (1, "802.11 b/g/n"), #~ ) #~ BLUETOOTH_CONNECTIVITY = ( #~ (1, "Bluetooth 4.0"), #~ (2, "Bluetooth 3.0"), #~ (3, "Bluetooth 2.1"), #~ ) #~ battery_type = models.PositiveSmallIntegerField( #~ _("Battery type"), #~ choices=BATTERY_TYPES, #~ ) #~ battery_capacity = models.PositiveIntegerField( #~ _("Capacity"), #~ help_text=_("Battery capacity in mAh"), #~ ) #~ ram_storage = models.PositiveIntegerField( #~ _("RAM"), #~ help_text=_("RAM storage in MB"), #~ ) #~ wifi_connectivity = models.PositiveIntegerField( #~ _("WiFi"), #~ choices=WIFI_CONNECTIVITY, #~ help_text=_("WiFi Connectivity"), #~ ) #~ bluetooth = models.PositiveIntegerField( #~ _("Bluetooth"), #~ choices=BLUETOOTH_CONNECTIVITY, #~ help_text=_("Bluetooth Connectivity"), #~ ) #~ gps = models.BooleanField( #~ _("GPS"), #~ default=False, #~ help_text=_("GPS integrated"), #~ ) genre = models.ForeignKey( Genre, verbose_name=_("Genre"), null=True, blank=True, ) width = models.DecimalField( _("Width"), max_digits=4, decimal_places=1, help_text=_("Width in mm"), ) height = models.DecimalField( _("Height"), max_digits=4, decimal_places=1, help_text=_("Height in mm"), ) #~ weight = models.DecimalField( #~ _("Weight"), #~ max_digits=5, #~ decimal_places=1, #~ help_text=_("Weight in gram"), #~ ) #~ screen_size = models.DecimalField( #~ _("Screen size"), #~ max_digits=4, #~ decimal_places=2, #~ help_text=_("Diagonal screen size in inch"), #~ ) multilingual = TranslatedFields(description=HTMLField( verbose_name=_("Description"), configuration='CKEDITOR_SETTINGS_DESCRIPTION', help_text= _("Full description used in the catalog's detail view of Smart Phones." ), blank=True, ), ) default_manager = TranslatableManager() class Meta: verbose_name = _("Kunsthaus Image") verbose_name_plural = _("Kunsthaus Images") def get_price(self, request): """ Return the starting price for instances of this smart phone model. """ if not hasattr(self, '_price'): if self.variants.exists(): currency = self.variants.first().unit_price.currency aggr = self.variants.aggregate(models.Min('unit_price')) self._price = MoneyMaker(currency)(aggr['unit_price__min']) else: self._price = Money() return self._price def is_in_cart(self, cart, watched=False, **kwargs): from shop.models.cart import CartItemModel try: product_code = kwargs['product_code'] except KeyError: return cart_item_qs = CartItemModel.objects.filter(cart=cart, product=self) for cart_item in cart_item_qs: if cart_item.product_code == product_code: return cart_item def get_product_variant(self, **kwargs): try: return self.variants.get(**kwargs) except SmartPhoneVariant.DoesNotExist as e: raise SmartPhoneModel.DoesNotExist(e)
class MarkdownDocument(TranslatableModelMixin, BaseModel): """Model representing a markdown document.""" RESOURCE_NAME = "markdown-documents" # Common LTI resource fields lti_id = models.CharField( max_length=255, verbose_name=_("lti id"), help_text=_("ID for synchronization with an external LTI tool"), blank=True, null=True, ) playlist = models.ForeignKey( to=Playlist, related_name="%(class)ss", verbose_name=_("playlist"), help_text=_("playlist to which this file belongs"), # don't allow hard deleting a playlist if it still contains files on_delete=models.PROTECT, ) position = models.PositiveIntegerField( verbose_name=_("position"), help_text=_("position of this file in the playlist"), default=0, ) duplicated_from = models.ForeignKey( to="self", related_name="duplicates", verbose_name=_("duplicated from"), help_text=_("original file this one was duplicated from"), # don't delete a file if the one it was duplicated from is hard deleted on_delete=models.SET_NULL, null=True, blank=True, ) # Common resource attribute created_by = models.ForeignKey( to=User, related_name="created_%(class)s", verbose_name=_("author"), help_text=_("author of the file"), # file is (soft-)deleted if author is (soft-)deleted on_delete=models.CASCADE, null=True, blank=True, ) is_public = models.BooleanField( default=False, verbose_name=_("is Markdown document public"), help_text=_("Is the Markdown document publicly accessible?"), ) # Specific Markdown document fields is_draft = models.BooleanField( default=True, verbose_name=_("is Markdown document still a draft"), help_text=_("Is the Markdown document a draft?"), ) translations = TranslatedFields( # Title might not be limited to 255 characters title=models.CharField( max_length=255, verbose_name=_("title"), help_text=_("Markdown document's title"), ), content=models.TextField( verbose_name=_("Markdown document content"), help_text=_("The document Markdown formatted content"), blank=True, ), rendered_content=models.TextField( verbose_name=_("Markdown document rendered content"), help_text=_("The Markdown document formatted content for students"), blank=True, ), ) rendering_options = models.JSONField( default=dict, verbose_name=_("Markdown rendering options"), help_text=_( "Markdown rendering options are use by the frontend's markdown to HTML renderer" ), blank=True, ) # use the translation manager, this is mandatory objects = TranslatableManager() class Meta: """Options for the ``MarkdownDocument`` model.""" db_table = "md_document" verbose_name = _("Markdown document") verbose_name_plural = _("Markdown documents") constraints = [ models.UniqueConstraint( fields=["lti_id", "playlist"], condition=models.Q(deleted=None), name="markdown_document_unique_idx", ) ] @staticmethod def get_ready_clause(): """Clause used in lti.utils.get_or_create_resource to filter the documents. Returns ------- models.Q A condition added to a QuerySet """ return models.Q(is_draft=False) def __str__(self): """Get the string representation of an instance, needs to be in an i18n context.""" result = f"{self.title}" if self.deleted: result = _("{:s} [deleted]").format(result) return result @property def consumer_site(self): """Return the consumer site linked to this file via the playlist.""" return self.playlist.consumer_site
class TranslatableModel(models.Model): """ Base model class to handle translations. All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`. """ class Meta: abstract = True # Consider these fields "protected" or "internal" attributes. # Not part of the public API, but used internally in the class hierarchy. _translations_field = None _translations_model = None language_code = LanguageCodeDescriptor() # change the default manager to the translation manager objects = TranslatableManager() def __init__(self, *args, **kwargs): # Still allow to pass the translated fields (e.g. title=...) to this function. translated_kwargs = {} current_language = None if kwargs: current_language = kwargs.pop('_current_language', None) for field in self._translations_model.get_translated_fields(): try: translated_kwargs[field] = kwargs.pop(field) except KeyError: pass # Run original Django model __init__ super(TranslatableModel, self).__init__(*args, **kwargs) self._translations_cache = {} self._current_language = normalize_language_code( current_language or get_language() ) # What you used to fetch the object is what you get. # Assign translated args manually. if translated_kwargs: translation = self._get_translated_model(auto_create=True) for field, value in six.iteritems(translated_kwargs): setattr(translation, field, value) def get_current_language(self): """ Get the current language. """ # not a property, so won't conflict with model fields. return self._current_language def set_current_language(self, language_code, initialize=False): """ Switch the currently activate language of the object. """ self._current_language = normalize_language_code(language_code or get_language()) # Ensure the translation is present for __get__ queries. if initialize: self._get_translated_model(use_fallback=False, auto_create=True) def get_fallback_language(self): """ Return the fallback language code, which is used in case there is no translation for the currently active language. """ lang_dict = get_language_settings(self._current_language) return lang_dict['fallback'] if lang_dict[ 'fallback'] != self._current_language else None def has_translation(self, language_code=None): """ Return whether a translation for the given language exists. Defaults to the current language code. """ if language_code is None: language_code = self._current_language try: # Check the local cache directly, and the answer is known. # NOTE this may also return newly auto created translations which are not saved yet. return self._translations_cache[language_code] is not None except KeyError: try: # Fetch from DB, fill the cache. self._get_translated_model(language_code, use_fallback=False, auto_create=False) except self._translations_model.DoesNotExist: return False else: return True def get_available_languages(self): """ Return the language codes of all translated variations. """ qs = self._get_translated_queryset() if qs._prefetch_done: return sorted(obj.language_code for obj in qs) else: return qs.values_list('language_code', flat=True).order_by('language_code') def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False): """ Fetch the translated fields model. """ if not self._translations_model or not self._translations_field: raise ImproperlyConfigured( "No translation is assigned to the current model!") if not language_code: language_code = self._current_language # 1. fetch the object from the local cache try: object = self._translations_cache[language_code] # If cached object indicates the language doesn't exist, need to query the fallback. if object is not None: return object except KeyError: # 2. No cache, need to query # Check that this object already exists, would be pointless otherwise to check for a translation. if not self._state.adding and self.pk: qs = self._get_translated_queryset() if qs._prefetch_done: # 2.1, use prefetched data # If the object is not found in the prefetched data (which contains all translations), # it's pointless to check for memcached (2.2) or perform a single query (2.3) for object in qs: if object.language_code == language_code: self._translations_cache[language_code] = object _cache_translation(object) # Store in memcached return object else: # 2.2, fetch from memcached object = get_cached_translation(self, language_code, use_fallback=use_fallback) if object is not None: # Track in local cache if object.language_code != language_code: self._translations_cache[ language_code] = None # Set fallback marker self._translations_cache[object.language_code] = object return object else: # 2.3, fetch from database try: object = qs.get(language_code=language_code) except self._translations_model.DoesNotExist: pass else: self._translations_cache[language_code] = object _cache_translation(object) # Store in memcached return object # Not in cache, or default. # Not fetched from DB # 3. Auto create? if auto_create: # Auto create policy first (e.g. a __set__ call) object = self._translations_model( language_code=language_code, master=self # ID might be None at this point ) self._translations_cache[language_code] = object # Not stored in memcached here yet, first fill + save it. return object # 4. Fallback? fallback_msg = None lang_dict = get_language_settings(language_code) if use_fallback and (lang_dict['fallback'] != language_code): # Explicitly set a marker for the fact that this translation uses the fallback instead. # Avoid making that query again. self._translations_cache[ language_code] = None # None value is the marker. if not self._state.adding or self.pk: _cache_translation_needs_fallback(self, language_code) # Jump to fallback language, return directly. # Don't cache under this language_code try: return self._get_translated_model(lang_dict['fallback'], use_fallback=False, auto_create=auto_create) except self._translations_model.DoesNotExist: fallback_msg = " (tried fallback {0})".format( lang_dict['fallback']) # None of the above, bail out! raise self._translations_model.DoesNotExist( "{0} does not have a translation for the current language!\n" "{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or '')) def _get_any_translated_model(self): """ Return any available translation. Returns None if there are no translations at all. """ if self._translations_cache: # There is already a language available in the case. No need for queries. # Give consistent answers if they exist. try: return self._translations_cache.get(self._current_language, None) \ or self._translations_cache.get(self.get_fallback_language(), None) \ or next(t for t in six.itervalues(self._translations_cache) if t if not None) # Skip fallback markers. except StopIteration: pass try: # Use prefetch if available, otherwise perform separate query. qs = self._get_translated_queryset() if qs._prefetch_done: translation = list(qs)[0] else: translation = qs[0] except IndexError: return None else: self._translations_cache[translation.language_code] = translation _cache_translation(translation) return translation def _get_translated_queryset(self): """ Return the queryset that points to the translated model. If there is a prefetch, it can be read from this queryset. """ # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache. accessor = getattr(self, self._translations_field) try: return accessor.get_queryset() except AttributeError: # Fallback for Django 1.4 and Django 1.5 return accessor.get_query_set() def save(self, *args, **kwargs): super(TranslatableModel, self).save(*args, **kwargs) self.save_translations(*args, **kwargs) def delete(self, using=None): _delete_cached_translations(self) super(TranslatableModel, self).delete(using) def save_translations(self, *args, **kwargs): """ The method to save all translations. This can be overwritten to implement any custom additions. This method calls :func:`save_translation` for every fetched language. :param args: Any custom arguments to pass to :func:`save`. :param kwargs: Any custom arguments to pass to :func:`save`. """ # Copy cache, new objects (e.g. fallbacks) might be fetched if users override save_translation() translations = self._translations_cache.values() # Save all translated objects which were fetched. # This also supports switching languages several times, and save everything in the end. for translation in translations: if translation is None: # Skip fallback markers continue self.save_translation(translation, *args, **kwargs) def save_translation(self, translation, *args, **kwargs): """ Save the translation when it's modified, or unsaved. :param translation: The translation :type translation: TranslatedFieldsModel :param args: Any custom arguments to pass to :func:`save`. :param kwargs: Any custom arguments to pass to :func:`save`. """ # Translation models without any fields are also supported. # This is useful for parent objects that have inlines; # the parent object defines how many translations there are. if translation.is_modified or (translation.is_empty and not translation.pk): if not translation.master_id: # Might not exist during first construction translation._state.db = self._state.db translation.master = self translation.save(*args, **kwargs) def safe_translation_getter(self, field, default=None, language_code=None, any_language=False): """ Fetch a translated property, and return a default value when both the translation and fallback language are missing. When ``any_language=True`` is used, the function also looks into other languages to find a suitable value. This feature can be useful for "title" attributes for example, to make sure there is at least something being displayed. Also consider using ``field = TranslatedField(any_language=True)`` in the model itself, to make this behavior the default for the given field. """ # By default, query via descriptor (TranslatedFieldDescriptor) # which also attempts the fallback language if configured to do so. tr_model = self # Extra feature: query a single field from a other translation. if language_code and language_code != self._current_language: # Try to fetch a cached value first. value = get_cached_translated_field(self, language_code, field) if value is not None: return value try: tr_model = self._get_translated_model(language_code) except TranslationDoesNotExist: pass # Use 'self' try: return getattr(tr_model, field) except TranslationDoesNotExist: pass if any_language: translation = self._get_any_translated_model() if translation is not None: return getattr(translation, field, default) return default
class TranslatableModel(models.Model): """ Base model class to handle translations. All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`. """ class Meta: abstract = True #: Access to the metadata of the translatable model _parler_meta = None #: Access to the language code language_code = LanguageCodeDescriptor() # change the default manager to the translation manager objects = TranslatableManager() def __init__(self, *args, **kwargs): # Still allow to pass the translated fields (e.g. title=...) to this function. translated_kwargs = {} current_language = None if kwargs: current_language = kwargs.pop('_current_language', None) for field in self._parler_meta.get_all_fields(): try: translated_kwargs[field] = kwargs.pop(field) except KeyError: pass # Have the attributes available, but they can't be ready yet; # self._state.adding is always True at this point, # the QuerySet.iterator() code changes it after construction. self._translations_cache = None self._current_language = None # Run original Django model __init__ super(TranslatableModel, self).__init__(*args, **kwargs) # Assign translated args manually. self._translations_cache = defaultdict(dict) self._current_language = normalize_language_code( current_language or get_language() ) # What you used to fetch the object is what you get. if translated_kwargs: self._set_translated_fields(self._current_language, **translated_kwargs) def _set_translated_fields(self, language_code=None, **fields): """ Assign fields to the translated models. """ objects = [] # no generator, make sure objects are all filled first for parler_meta, model_fields in self._parler_meta._split_fields( **fields): translation = self._get_translated_model( language_code=language_code, auto_create=True, meta=parler_meta) for field, value in six.iteritems(model_fields): setattr(translation, field, value) objects.append(translation) return objects def create_translation(self, language_code, **fields): """ Add a translation to the model. The :func:`save_translations` function is called afterwards. The object will be saved immediately, similar to calling :func:`~django.db.models.manager.Manager.create` or :func:`~django.db.models.fields.related.RelatedManager.create` on related fields. """ meta = self._parler_meta if self._translations_cache[meta.root_model].get( language_code, None): # MISSING evaluates to False too raise ValueError( "Translation already exists: {0}".format(language_code)) # Save all fields in the proper translated model. for translation in self._set_translated_fields(language_code, **fields): self.save_translation(translation) def get_current_language(self): """ Get the current language. """ # not a property, so won't conflict with model fields. return self._current_language def set_current_language(self, language_code, initialize=False): """ Switch the currently activate language of the object. """ self._current_language = normalize_language_code(language_code or get_language()) # Ensure the translation is present for __get__ queries. if initialize: self._get_translated_model(use_fallback=False, auto_create=True) def get_fallback_language(self): """ Return the fallback language code, which is used in case there is no translation for the currently active language. """ lang_dict = get_language_settings(self._current_language) return lang_dict['fallback'] if lang_dict[ 'fallback'] != self._current_language else None def has_translation(self, language_code=None, related_name=None): """ Return whether a translation for the given language exists. Defaults to the current language code. .. versionadded 1.2 Added the ``related_name`` parameter. """ if language_code is None: language_code = self._current_language meta = self._parler_meta._get_extension_by_related_name(related_name) try: # Check the local cache directly, and the answer is known. # NOTE this may also return newly auto created translations which are not saved yet. return self._translations_cache[ meta.model][language_code] is not MISSING except KeyError: # Try to fetch from the cache first. # If the cache returns the fallback, it means the original does not exist. object = get_cached_translation(self, language_code, related_name=related_name, use_fallback=True) if object is not None: return object.language_code == language_code try: # Fetch from DB, fill the cache. self._get_translated_model(language_code, use_fallback=False, auto_create=False, meta=meta) except meta.model.DoesNotExist: return False else: return True def get_available_languages(self, related_name=None, include_unsaved=False): """ Return the language codes of all translated variations. .. versionadded 1.2 Added the ``include_unsaved`` and ``related_name`` parameters. """ meta = self._parler_meta._get_extension_by_related_name(related_name) prefetch = self._get_prefetched_translations(meta=meta) if prefetch is not None: db_languages = sorted(obj.language_code for obj in prefetch) else: qs = self._get_translated_queryset(meta=meta) db_languages = qs.values_list('language_code', flat=True).order_by('language_code') if include_unsaved: local_languages = ( k for k, v in six.iteritems(self._translations_cache[meta.model]) if v is not MISSING) return list(set(db_languages) | set(local_languages)) else: return db_languages def get_translation(self, language_code, related_name=None): """ Fetch the translated model """ meta = self._parler_meta._get_extension_by_related_name(related_name) return self._get_translated_model(language_code, meta=meta) def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False, meta=None): """ Fetch the translated fields model. """ if self._parler_meta is None: raise ImproperlyConfigured( "No translation is assigned to the current model!") if self._translations_cache is None: raise RuntimeError( "Accessing translated fields before super.__init__() is not possible." ) if not language_code: language_code = self._current_language if meta is None: meta = self._parler_meta.root # work on base model by default local_cache = self._translations_cache[meta.model] # 1. fetch the object from the local cache try: object = local_cache[language_code] # If cached object indicates the language doesn't exist, need to query the fallback. if object is not MISSING: return object except KeyError: # 2. No cache, need to query # Check that this object already exists, would be pointless otherwise to check for a translation. if not self._state.adding and self.pk is not None: prefetch = self._get_prefetched_translations(meta=meta) if prefetch is not None: # 2.1, use prefetched data # If the object is not found in the prefetched data (which contains all translations), # it's pointless to check for memcached (2.2) or perform a single query (2.3) for object in prefetch: if object.language_code == language_code: local_cache[language_code] = object _cache_translation(object) # Store in memcached return object else: # 2.2, fetch from memcached object = get_cached_translation(self, language_code, related_name=meta.rel_name, use_fallback=use_fallback) if object is not None: # Track in local cache if object.language_code != language_code: local_cache[ language_code] = MISSING # Set fallback marker local_cache[object.language_code] = object return object elif local_cache.get(language_code, None) is MISSING: # If get_cached_translation() explicitly set the "does not exist" marker, # there is no need to try a database query. pass else: # 2.3, fetch from database try: object = self._get_translated_queryset(meta).get( language_code=language_code) except meta.model.DoesNotExist: pass else: local_cache[language_code] = object _cache_translation(object) # Store in memcached return object # Not in cache, or default. # Not fetched from DB # 3. Auto create? if auto_create: # Auto create policy first (e.g. a __set__ call) kwargs = { 'language_code': language_code, } if self.pk: # ID might be None at this point, and Django 1.8 does not allow that. kwargs['master'] = self object = meta.model(**kwargs) local_cache[language_code] = object # Not stored in memcached here yet, first fill + save it. return object # 4. Fallback? fallback_msg = None lang_dict = get_language_settings(language_code) if language_code not in local_cache: # Explicitly set a marker for the fact that this translation uses the fallback instead. # Avoid making that query again. local_cache[language_code] = MISSING # None value is the marker. if not self._state.adding or self.pk is not None: _cache_translation_needs_fallback(self, language_code, related_name=meta.rel_name) if lang_dict['fallback'] != language_code and use_fallback: # Jump to fallback language, return directly. # Don't cache under this language_code try: return self._get_translated_model(lang_dict['fallback'], use_fallback=False, auto_create=auto_create, meta=meta) except meta.model.DoesNotExist: fallback_msg = " (tried fallback {0})".format( lang_dict['fallback']) # None of the above, bail out! raise meta.model.DoesNotExist( "{0} does not have a translation for the current language!\n" "{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or '')) def _get_any_translated_model(self, meta=None): """ Return any available translation. Returns None if there are no translations at all. """ if meta is None: meta = self._parler_meta.root tr_model = meta.model local_cache = self._translations_cache[tr_model] if local_cache: # There is already a language available in the case. No need for queries. # Give consistent answers if they exist. try: return local_cache.get(self._current_language, None) \ or local_cache.get(self.get_fallback_language(), None) \ or next(t for t in six.itervalues(local_cache) if t is not MISSING) # Skip fallback markers. except StopIteration: pass try: # Use prefetch if available, otherwise perform separate query. prefetch = self._get_prefetched_translations(meta=meta) if prefetch is not None: translation = prefetch[0] # Already a list else: translation = self._get_translated_queryset(meta=meta)[0] except IndexError: return None else: local_cache[translation.language_code] = translation _cache_translation(translation) return translation def _get_translated_queryset(self, meta=None): """ Return the queryset that points to the translated model. If there is a prefetch, it can be read from this queryset. """ # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache. if meta is None: meta = self._parler_meta.root accessor = getattr(self, meta.rel_name) if django.VERSION >= (1, 6): # Call latest version return accessor.get_queryset() else: # Must call RelatedManager.get_query_set() and avoid calling a custom get_queryset() # method for packages with Django 1.6/1.7 compatibility. return accessor.get_query_set() def _get_prefetched_translations(self, meta=None): """ Return the queryset with prefetch results. """ if meta is None: meta = self._parler_meta.root related_name = meta.rel_name try: # Read the list directly, avoid QuerySet construction. # Accessing self._get_translated_queryset(parler_meta)._prefetch_done is more expensive. return self._prefetched_objects_cache[related_name] except (AttributeError, KeyError): return None def save(self, *args, **kwargs): super(TranslatableModel, self).save(*args, **kwargs) # Makes no sense to add these for translated model # Even worse: mptt 0.7 injects this parameter when it avoids updating the lft/rgt fields, # but that misses all the translated fields. kwargs.pop('update_fields', None) self.save_translations(*args, **kwargs) def delete(self, using=None): _delete_cached_translations(self) super(TranslatableModel, self).delete(using) def validate_unique(self, exclude=None): """ Also validate the unique_together of the translated model. """ # This is called from ModelForm._post_clean() or Model.full_clean() errors = {} try: super(TranslatableModel, self).validate_unique(exclude=exclude) except ValidationError as e: errors = e.message_dict # Django 1.5 + 1.6 compatible for local_cache in six.itervalues(self._translations_cache): for translation in six.itervalues(local_cache): if translation is MISSING: # Skip fallback markers continue try: translation.validate_unique(exclude=exclude) except ValidationError as e: errors.update(e.message_dict) if errors: raise ValidationError(errors) def save_translations(self, *args, **kwargs): """ The method to save all translations. This can be overwritten to implement any custom additions. This method calls :func:`save_translation` for every fetched language. :param args: Any custom arguments to pass to :func:`save`. :param kwargs: Any custom arguments to pass to :func:`save`. """ # Copy cache, new objects (e.g. fallbacks) might be fetched if users override save_translation() # Not looping over the cache, but using _parler_meta so the translations are processed in the order of inheritance. local_caches = self._translations_cache.copy() for meta in self._parler_meta: local_cache = local_caches[meta.model] translations = list(local_cache.values()) # Save all translated objects which were fetched. # This also supports switching languages several times, and save everything in the end. for translation in translations: if translation is MISSING: # Skip fallback markers continue self.save_translation(translation, *args, **kwargs) def save_translation(self, translation, *args, **kwargs): """ Save the translation when it's modified, or unsaved. .. note:: When a derived model provides additional translated fields, this method receives both the original and extended translation. To distinguish between both objects, check for ``translation.related_name``. :param translation: The translation :type translation: TranslatedFieldsModel :param args: Any custom arguments to pass to :func:`save`. :param kwargs: Any custom arguments to pass to :func:`save`. """ if self.pk is None or self._state.adding: raise RuntimeError( "Can't save translations when the master object is not yet saved." ) # Translation models without any fields are also supported. # This is useful for parent objects that have inlines; # the parent object defines how many translations there are. if translation.is_modified or (translation.is_empty and not translation.pk): if not translation.master_id: # Might not exist during first construction translation._state.db = self._state.db translation.master = self translation.save(*args, **kwargs) def safe_translation_getter(self, field, default=None, language_code=None, any_language=False): """ Fetch a translated property, and return a default value when both the translation and fallback language are missing. When ``any_language=True`` is used, the function also looks into other languages to find a suitable value. This feature can be useful for "title" attributes for example, to make sure there is at least something being displayed. Also consider using ``field = TranslatedField(any_language=True)`` in the model itself, to make this behavior the default for the given field. """ meta = self._parler_meta._get_extension_by_field(field) # Extra feature: query a single field from a other translation. if language_code and language_code != self._current_language: try: tr_model = self._get_translated_model(language_code, meta=meta, use_fallback=True) return getattr(tr_model, field) except TranslationDoesNotExist: pass else: # By default, query via descriptor (TranslatedFieldDescriptor) # which also attempts the fallback language if configured to do so. try: return getattr(self, field) except TranslationDoesNotExist: pass if any_language: translation = self._get_any_translated_model(meta=meta) if translation is not None: return getattr(translation, field, default) return default
class ScooterModel(Product): """ A generic smart phone model, which must be concretized by a model `SmartPhone` - see below. """ BATTERY_TYPES = ( (1, "LG battery"), (2, "Chinese battery"), ) #~ WIFI_CONNECTIVITY = ( #~ (1, "802.11 b/g/n"), #~ ) #~ BLUETOOTH_CONNECTIVITY = ( #~ (1, "Bluetooth 4.0"), #~ (2, "Bluetooth 3.0"), #~ (3, "Bluetooth 2.1"), #~ ) battery_type = models.PositiveSmallIntegerField( _("Battery type"), choices=BATTERY_TYPES, ) battery_capacity = models.DecimalField( _("Battery capacity"), max_digits=3, decimal_places=1, help_text=_("Battery capacity in Ah"), null=True, ) #~ ram_storage = models.PositiveIntegerField( #~ _("RAM"), #~ help_text=_("RAM storage in MB"), #~ ) #~ wifi_connectivity = models.PositiveIntegerField( #~ _("WiFi"), #~ choices=WIFI_CONNECTIVITY, #~ help_text=_("WiFi Connectivity"), #~ ) #~ bluetooth = models.PositiveIntegerField( #~ _("Bluetooth"), #~ choices=BLUETOOTH_CONNECTIVITY, #~ help_text=_("Bluetooth Connectivity"), #~ ) bluetooth = models.BooleanField( _("Bluetooth"), default=True, help_text=_("Bluetooth Connectivity"), ) operating_system = models.ForeignKey( OperatingSystem, verbose_name=_("Operating System"), ) width = models.DecimalField( _("Width"), max_digits=5, decimal_places=0, help_text=_("Width in cm"), ) height = models.DecimalField( _("Height"), max_digits=5, decimal_places=0, help_text=_("Height in cm"), ) depth = models.DecimalField( _("Depth"), max_digits=5, decimal_places=0, help_text=_("Height in cm"), ) weight = models.DecimalField( _("Weight"), max_digits=5, decimal_places=1, help_text=_("Weight in kg"), ) #~ screen_size = models.DecimalField( #~ _("Screen size"), #~ max_digits=4, #~ decimal_places=2, #~ help_text=_("Diagonal screen size in inch"), #~ ) max_speed = models.DecimalField( _("Maxumum speed"), max_digits=4, decimal_places=0, help_text=_("Maxumum speed in km/h"), ) mileage = models.DecimalField( _("Maxumum mileage"), max_digits=4, decimal_places=0, help_text=_("Maxumum mileage per one battery charge in km"), ) power = models.DecimalField( _("Motor power"), max_digits=4, decimal_places=0, help_text=_("Motor power in km"), ) multilingual = TranslatedFields(description=HTMLField( verbose_name=_("Description"), configuration='CKEDITOR_SETTINGS_DESCRIPTION', help_text=_( "Full description used in the catalog's detail view of Scooters."), ), ) default_manager = TranslatableManager() #~ placeholder = PlaceholderField('product_details') class Meta: verbose_name = _("Electric Scooter") verbose_name_plural = _("Electric Scooters") def get_price(self, request): """ Return the starting price for instances of this smart phone model. """ if not hasattr(self, '_price'): if self.variants.exists(): currency = self.variants.first().unit_price.currency aggr = self.variants.aggregate(models.Min('unit_price')) self._price = MoneyMaker(currency)(aggr['unit_price__min']) else: self._price = Money() return self._price def is_in_cart(self, cart, watched=False, **kwargs): from shop.models.cart import CartItemModel try: product_code = kwargs['product_code'] except KeyError: return cart_item_qs = CartItemModel.objects.filter(cart=cart, product=self) for cart_item in cart_item_qs: if cart_item.product_code == product_code: return cart_item def get_product_variant(self, **kwargs): try: return self.variants.get(**kwargs) except ScooterVariant.DoesNotExist as e: raise ScooterModel.DoesNotExist(e)
class TranslatableModel(models.Model): """ Base model class to handle translations. """ # Consider these fields "protected" or "internal" attributes. # Not part of the public API, but used internally in the class hierarchy. _translations_field = None _translations_model = None language_code = LanguageCodeDescriptor() # change the default manager to the translation manager objects = TranslatableManager() class Meta: abstract = True def __init__(self, *args, **kwargs): # Still allow to pass the translated fields (e.g. title=...) to this function. translated_kwargs = {} current_language = None if kwargs: current_language = kwargs.pop('_current_language', None) for field in self._translations_model.get_translated_fields(): try: translated_kwargs[field] = kwargs.pop(field) except KeyError: pass # Run original Django model __init__ super(TranslatableModel, self).__init__(*args, **kwargs) self._translations_cache = {} self._current_language = normalize_language_code( current_language or get_language() ) # What you used to fetch the object is what you get. # Assign translated args manually. if translated_kwargs: translation = self._get_translated_model(auto_create=True) for field, value in translated_kwargs.iteritems(): setattr(translation, field, value) def get_current_language(self): """ Get the current language. """ # not a property, so won't conflict with model fields. return self._current_language def set_current_language(self, language_code, initialize=False): """ Switch the currently activate language of the object. """ self._current_language = normalize_language_code(language_code or get_language()) # Ensure the translation is present for __get__ queries. if initialize: self._get_translated_model(use_fallback=False, auto_create=True) def get_fallback_language(self): """ Return the fallback language code, which is used in case there is no translation for the currently active language. """ lang_dict = get_language_settings(self._current_language) return lang_dict['fallback'] if lang_dict[ 'fallback'] != self._current_language else None def has_translation(self, language_code=None): """ Return whether a translation for the given language exists. Defaults to the current language code. """ if language_code is None: language_code = self._current_language try: # Check the local cache directly, and the answer is known. # NOTE this may also return newly auto created translations which are not saved yet. return self._translations_cache[language_code] is not None except KeyError: try: # Fetch from DB, fill the cache. self._get_translated_model(language_code, use_fallback=False, auto_create=False) except self._translations_model.DoesNotExist: return False else: return True def get_available_languages(self): """ Return the language codes of all translated variations. """ return self._translations_model.objects.using(self._state.db).filter( master=self).values_list('language_code', flat=True).order_by('language_code') def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False): """ Fetch the translated fields model. """ if not self._translations_model or not self._translations_field: raise ImproperlyConfigured( "No translation is assigned to the current model!") if not language_code: language_code = self._current_language # 1. fetch the object from the local cache try: object = self._translations_cache[language_code] # If cached object indicates the language doesn't exist, need to query the fallback. if object is not None: return object except KeyError: # 2. No cache, need to query # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache. # Check that this object already exists, would be pointless otherwise to check for a translation. if not self._state.adding: # 2.1, fetch from memcache object = get_cached_translation(self, language_code) if object is not None: # Track in local cache self._translations_cache[language_code] = object return object else: # 2.2, fetch from database accessor = getattr(self, self._translations_field) try: object = accessor.get(language_code=language_code) except self._translations_model.DoesNotExist: pass else: self._translations_cache[language_code] = object _cache_translation(object) # Store in memcached return object # Not in cache, or default. # Not fetched from DB # 3. Auto create? if auto_create: # Auto create policy first (e.g. a __set__ call) object = self._translations_model( language_code=language_code, master=self # ID might be None at this point ) self._translations_cache[language_code] = object # Not stored in memcached here yet, first fill + save it. return object # 4. Fallback? fallback_msg = None lang_dict = get_language_settings(language_code) if use_fallback and (lang_dict['fallback'] != language_code): # Jump to fallback language, return directly. # Don't cache under this language_code self._translations_cache[ language_code] = None # explicit marker that language query was tried before. try: return self._get_translated_model(lang_dict['fallback'], use_fallback=False, auto_create=auto_create) except self._translations_model.DoesNotExist: fallback_msg = u" (tried fallback {0})".format( lang_dict['fallback']) # None of the above, bail out! raise self._translations_model.DoesNotExist( u"{0} does not have a translation for the current language!\n" u"{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or '')) def _get_any_translated_model(self): """ Return any available translation. Returns None if there are no translations at all. """ if self._translations_cache: # There is already a language available in the case. No need for queries. # Give consistent answers if they exist. try: return self._translations_cache.get(self._current_language, None) \ or self._translations_cache.get(self.get_fallback_language(), None) \ or next(t for t in self._translations_cache.itervalues() if t if not None) # Skip fallback markers. except StopIteration: pass try: translation = self._translations_model.objects.using( self._state.db).filter(master=self)[0] except IndexError: return None else: self._translations_cache[translation.language_code] = translation _cache_translation(translation) return translation def save(self, *args, **kwargs): super(TranslatableModel, self).save(*args, **kwargs) self.save_translations(*args, **kwargs) def delete(self, using=None): _delete_cached_translations(self) super(TranslatableModel, self).delete(using) def save_translations(self, *args, **kwargs): # Save all translated objects which were fetched. # This also supports switching languages several times, and save everything in the end. for translation in self._translations_cache.itervalues(): if translation is None: # Skip fallback markers continue self.save_translation(translation, *args, **kwargs) def save_translation(self, translation, *args, **kwargs): # Translation models without any fields are also supported. # This is useful for parent objects that have inlines; # the parent object defines how many translations there are. if translation.is_modified or (translation.is_empty and not translation.pk): if not translation.master_id: # Might not exist during first construction translation._state.db = self._state.db translation.master = self translation.save(*args, **kwargs) def safe_translation_getter(self, field, default=None, any_language=False): """ Fetch a translated property, and return a default value when both the translation and fallback language are missing. When ``any_language=True`` is used, the function also looks into other languages to find a suitable value. This feature can be useful for "title" attributes for example, to make sure there is at least something being displayed. Also consider using ``field = TranslatedField(any_language=True)`` in the model itself, to make this behavior the default for the given field. """ try: return getattr(self, field) except TranslationDoesNotExist: pass if any_language: translation = self._get_any_translated_model() if translation is not None: return getattr(translation, field, default) return default
class Migration(migrations.Migration): dependencies = [ ('fluent_contents', '0001_initial'), ('geo', '0004_auto_20160929_0817'), ('cms', '0028_merge_20171006_1622'), ] operations = [ migrations.CreateModel( name='CategoriesContent', fields=[ ('contentitem_ptr', models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fluent_contents.ContentItem')), ('title', models.CharField(blank=True, max_length=40, null=True)), ('sub_title', models.CharField(blank=True, max_length=70, null=True)), ('categories', models.ManyToManyField( db_table=b'cms_categoriescontent_categories', to='categories.Category')), ], options={ 'db_table': 'contentitem_cms_categoriescontent', 'verbose_name': 'Categories', }, bases=('fluent_contents.contentitem', ), managers=[ ('objects', ContentItemManager()), ('base_objects', django.db.models.manager.Manager()), ], ), migrations.CreateModel( name='LocationsContent', fields=[ ('contentitem_ptr', models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fluent_contents.ContentItem')), ('title', models.CharField(blank=True, max_length=40, null=True)), ('sub_title', models.CharField(blank=True, max_length=70, null=True)), ('locations', models.ManyToManyField( db_table=b'cms_locationscontent_locations', to='geo.Location')), ], options={ 'db_table': 'contentitem_cms_locationscontent', 'verbose_name': 'Locations', }, bases=('fluent_contents.contentitem', ), managers=[ ('objects', ContentItemManager()), ('base_objects', django.db.models.manager.Manager()), ], ), migrations.CreateModel( name='Slide', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ], options={ 'abstract': False, }, bases=(models.Model, ), managers=[ ('objects', TranslatableManager()), ('base_objects', django.db.models.manager.Manager()), ], ), migrations.CreateModel( name='SlidesContent', fields=[ ('contentitem_ptr', models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fluent_contents.ContentItem')), ], options={ 'db_table': 'contentitem_cms_slidescontent', 'verbose_name': 'Slides', }, bases=('fluent_contents.contentitem', ), managers=[ ('objects', ContentItemManager()), ('base_objects', django.db.models.manager.Manager()), ], ), migrations.CreateModel( name='SlideTranslation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')), ('tab_text', models.CharField( help_text='This is shown on tabs beneath the banner.', max_length=100, verbose_name='Tab text')), ('title', models.CharField(blank=True, max_length=100, verbose_name='Title')), ('body', models.TextField(blank=True, verbose_name='Body text')), ('image', bluebottle.utils.fields.ImageField( blank=True, max_length=255, null=True, upload_to=b'banner_slides/', verbose_name='Image')), ('background_image', bluebottle.utils.fields.ImageField( blank=True, max_length=255, null=True, upload_to=b'banner_slides/', verbose_name='Background image')), ('video_url', models.URLField(blank=True, default=b'', max_length=100, verbose_name='Video url')), ('link_text', models.CharField( blank=True, help_text= 'This is the text on the button inside the banner.', max_length=400, verbose_name='Link text')), ('link_url', models.CharField( blank=True, help_text= 'This is the link for the button inside the banner.', max_length=400, verbose_name='Link url')), ('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='cms.Slide')), ], options={ 'managed': True, 'db_table': 'cms_slide_translation', 'db_tablespace': '', 'default_permissions': (), 'verbose_name': 'slide Translation', }, ), migrations.CreateModel( name='Step', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('image', bluebottle.utils.fields.ImageField(blank=True, max_length=255, null=True, upload_to=b'step_images/', verbose_name='Image')), ], options={ 'abstract': False, }, bases=(models.Model, ), ), migrations.CreateModel( name='StepsContent', fields=[ ('contentitem_ptr', models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fluent_contents.ContentItem')), ('title', models.CharField(blank=True, max_length=40, null=True)), ('sub_title', models.CharField(blank=True, max_length=70, null=True)), ('action_text', models.CharField(blank=True, default='Start your own project', max_length=40, null=True)), ('action_link', models.CharField(blank=True, default=b'/start-project', max_length=100, null=True)), ], options={ 'db_table': 'contentitem_cms_stepscontent', 'verbose_name': 'Steps', }, bases=('fluent_contents.contentitem', ), managers=[ ('objects', ContentItemManager()), ('base_objects', django.db.models.manager.Manager()), ], ), migrations.CreateModel( name='StepTranslation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')), ('header', models.CharField(max_length=100, verbose_name='Header')), ('text', models.CharField(max_length=400, verbose_name='Text')), ('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='cms.Step')), ], options={ 'managed': True, 'db_table': 'cms_step_translation', 'db_tablespace': '', 'default_permissions': (), 'verbose_name': 'step Translation', }, ), migrations.RemoveField( model_name='metric', name='block', ), migrations.RemoveField( model_name='metricscontent', name='contentitem_ptr', ), migrations.AlterUniqueTogether( name='metrictranslation', unique_together=set([]), ), migrations.RemoveField( model_name='metrictranslation', name='master', ), migrations.DeleteModel(name='Metric', ), migrations.DeleteModel(name='MetricsContent', ), migrations.DeleteModel(name='MetricTranslation', ), migrations.AddField( model_name='step', name='block', field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name='steps', to='cms.StepsContent'), ), migrations.AddField( model_name='slide', name='block', field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name='slides', to='cms.SlidesContent'), ), migrations.AlterUniqueTogether( name='steptranslation', unique_together=set([('language_code', 'master')]), ), migrations.AlterUniqueTogether( name='slidetranslation', unique_together=set([('language_code', 'master')]), ) ]