예제 #1
0
class Slide(models.Model):

    slider = models.ForeignKey(Slider, on_delete=models.CASCADE)
    image = FilerImageField(
        help_text=
        'Please supply an image for this slide. The recommended image size is 1920px width, and 670px height.'
    )
    title = HTMLField('',
                      blank=True,
                      null=True,
                      help_text='Please supply a title for the slide')
    top_title = models.IntegerField('Top',
                                    default=100,
                                    blank=True,
                                    null=True,
                                    help_text='Top position')
    left_title = models.IntegerField('Left',
                                     default=100,
                                     blank=True,
                                     null=True,
                                     help_text='Left position')
    size_title = models.PositiveIntegerField('Size',
                                             default=100,
                                             blank=True,
                                             null=True,
                                             help_text='Font size')
    color_title = ColorField('Color', default='#FFF')
    text = HTMLField('',
                     blank=True,
                     null=True,
                     help_text='Please supply a description for the slide')
    top_text = models.IntegerField('Top',
                                   default=300,
                                   blank=True,
                                   null=True,
                                   help_text='Top position')
    left_text = models.IntegerField('Left',
                                    default=100,
                                    blank=True,
                                    null=True,
                                    help_text='Left position')
    size_text = models.PositiveIntegerField('Size',
                                            default=50,
                                            blank=True,
                                            null=True,
                                            help_text='Font size')
    color_text = ColorField('Color', default='#FFF')
    show_button = models.BooleanField('Show',
                                      default=True,
                                      help_text='Show button for this slide?')
    text_button = models.CharField('Text',
                                   default='Show More',
                                   max_length=255,
                                   blank=True,
                                   null=True)
    url_button = models.CharField('Url',
                                  max_length=255,
                                  blank=True,
                                  null=True,
                                  default='#')
    color_button = ColorField('Color Background', default='#62e0c1')
    top_button = models.IntegerField('Top',
                                     default=500,
                                     blank=True,
                                     null=True,
                                     help_text='Top position')
    left_button = models.IntegerField('Left',
                                      default=1000,
                                      blank=True,
                                      null=True,
                                      help_text='Left position')
    is_active = models.BooleanField(default=True,
                                    help_text='Is active this slide?')
    position = models.DecimalField(max_digits=5,
                                   decimal_places=1,
                                   default=0,
                                   blank=True,
                                   null=True,
                                   help_text='Position Slide')

    def __str__(self):
        if self.title != None:
            return self.title
        if self.text != None:
            return self.text
        return 'Slide' + str(self.id)
예제 #2
0
class LocalImageAssetTranslation(AssetTranslation):
    """Translatable fields for a LocalImageAsset model instance"""
    image = FilerImageField(null=True)
예제 #3
0
class Bootstrap4GridColumn(CMSPlugin):
    """
    Layout > Grid: "Column" Plugin
    https://getbootstrap.com/docs/4.0/layout/grid/
    """
    layout = models.CharField(
        verbose_name=_('Layout'),
        #choices=GRID_COL_LAYOUT_CHOICES,
        blank=True,
        max_length=255,
        help_text=_('Select a layout'),
    )
    column_type = models.CharField(
        verbose_name=_('Column type'),
        choices=GRID_COLUMN_CHOICES,
        default=GRID_COLUMN_CHOICES[0][0],
        blank=True,
        max_length=255,
    )
    column_size = IntegerRangeField(
        verbose_name=_('Column size'),
        blank=True,
        null=True,
        min_value=0,
        max_value=GRID_SIZE,
        help_text=_(
            'Nummeric value from 1 - {bound}. '
            'Spreads the columns evenly when empty.').format(bound=GRID_SIZE))
    column_alignment = models.CharField(
        verbose_name=_('Alignment'),
        choices=GRID_COLUMN_ALIGNMENT_CHOICES,
        blank=True,
        max_length=255,
    )
    tag_type = TagTypeField()
    attributes = AttributesField()
    background_color = RGBColorField(verbose_name=_('Background Color'),
                                     blank=True,
                                     null=True)
    background_image = FilerImageField(verbose_name=_('Background Image'),
                                       on_delete=models.SET_NULL,
                                       null=True,
                                       blank=True,
                                       related_name='col_bg_image')
    background_svg = FilerFileField(verbose_name=_('Background SVG'),
                                    on_delete=models.SET_NULL,
                                    null=True,
                                    blank=True,
                                    related_name='+')
    title = models.CharField(
        verbose_name=_('Title'),
        null=True,
        blank=True,
        max_length=255,
    )
    display_title = models.BooleanField(
        verbose_name=_('Display Title'),
        default=False,
    )

    def __str__(self):
        return str(self.pk)

    def get_short_description(self):
        text = ''
        classes = self.get_grid_values()
        if self.column_size:
            text += '(col-{}) '.format(self.column_size)
        else:
            text += '(auto) '
        if self.column_type != 'col':
            text += '.{} '.format(self.column_type)
        if classes:
            text += '.{}'.format(' .'.join(self.get_grid_values()))
        return text

    def get_grid_values(self):
        classes = []
        hide = True
        for device in DEVICE_SIZES:
            for element in ('col', 'order', 'ml', 'mr', 'hide'):
                size = getattr(self, '{}_{}'.format(device, element))
                if size:
                    if element == 'hide':
                        if device == 'xs':
                            classes.append('{}-{}'.format('d', 'none'))
                        else:
                            classes.append('{}-{}-{}'.format(
                                'd', device, 'none'))
                        hide = True
                    elif element == 'col':
                        classes.append('{}-{}-{}'.format(
                            element, device, int(size)))
                    elif element == 'order':
                        if device == 'xs':
                            classes.append('{}-{}'.format(element, int(size)))
                        else:
                            classes.append('{}-{}-{}'.format(
                                element, device, int(size)))
                    else:
                        classes.append('{}-{}-{}'.format(
                            element, device, 'auto'))
                else:
                    if hide and element == 'hide':
                        if device == 'xs':
                            classes.append('{}-{}'.format('d', 'block'))
                        else:
                            classes.append('{}-{}-{}'.format(
                                'd', device, 'block'))
                        hide = False

        return classes
예제 #4
0
class NewsBlogJSRelatedPlugin(PluginEditModeMixin, AdjustableCacheModelMixin,
                              CMSPlugin):
    # NOTE: This one does NOT subclass NewsBlogCMSPlugin. This is because this
    # plugin can really only be placed on the article detail view in an apphook.
    cmsplugin_ptr = models.OneToOneField(CMSPlugin,
                                         on_delete=models.CASCADE,
                                         related_name='+',
                                         parent_link=True)

    title = models.CharField(max_length=255,
                             blank=True,
                             verbose_name=_('Title'))
    description = models.TextField(verbose_name=_('description'),
                                   blank=True,
                                   default='')
    icon = Icon(blank=False, default='')
    image = FilerImageField(on_delete=models.SET_NULL,
                            null=True,
                            blank=True,
                            related_name="title_image")
    number_of_articles = models.PositiveSmallIntegerField(
        verbose_name=_('Number of articles'),
        validators=[django.core.validators.MaxValueValidator(500)])
    layout = models.CharField(max_length=30, verbose_name=_('layout'))
    featured = models.BooleanField(blank=True, default=False)
    exclude_current_article = models.BooleanField(blank=True, default=False)
    related_types = SortedManyToManyField(NewsBlogConfig,
                                          verbose_name=_('related sections'),
                                          blank=True,
                                          symmetrical=False)
    related_mediums = SortedManyToManyField(ArticleMedium,
                                            verbose_name=_('medium'),
                                            blank=True,
                                            symmetrical=False)
    related_categories = SortedManyToManyField(
        Category,
        verbose_name=_('related categories'),
        blank=True,
        symmetrical=False)
    related_service_sections = SortedManyToManyField(
        'js_services.ServicesConfig',
        verbose_name=_('related service section'),
        blank=True,
        symmetrical=False)
    related_services = SortedManyToManyField(
        'js_services.Service',
        verbose_name=_('related services'),
        blank=True,
        symmetrical=False)
    related_authors = SortedManyToManyField(Person,
                                            verbose_name=_('related authors'),
                                            blank=True,
                                            symmetrical=False)
    more_button_is_shown = models.BooleanField(
        blank=True, default=False, verbose_name=_('Show “See More Button”'))
    more_button_text = models.CharField(max_length=255,
                                        blank=True,
                                        verbose_name=_('See More Button Text'))
    more_button_link = models.CharField(max_length=255,
                                        blank=True,
                                        verbose_name=_('See More Button Link'))

    def copy_relations(self, oldinstance):
        self.related_types.set(oldinstance.related_types.all())
        self.related_mediums.set(oldinstance.related_mediums.all())
        self.related_categories.set(oldinstance.related_categories.all())
        self.related_service_sections.set(
            oldinstance.related_service_sections.all())
        self.related_services.set(oldinstance.related_services.all())
        self.related_authors.set(oldinstance.related_authors.all())
        if IS_THERE_COMPANIES:
            self.related_companies.set(oldinstance.related_companies.all())

    # def get_articles(self, article, request):
    #     """
    #     Returns a queryset of articles that are related to the given article.
    #     """
    #     languages = get_valid_languages_from_request(
    #         article.app_config.namespace, request)
    #     if self.language not in languages:
    #         return Article.objects.none()
    #     qs = article.related.translated(*languages)
    #     if not self.get_edit_mode(request):
    #         qs = qs.published()
    #     return qs

    def __str__(self):
        return ugettext('Related articles')
예제 #5
0
class Service(TranslatableShuupModel):
    """
    Abstract base model for services.

    Each enabled service should be linked to a service provider and
    should have a choice identifier specified in its `choice_identifier`
    field.  The choice identifier should be valid for the service
    provider, i.e. it should be one of the `ServiceChoice.identifier`
    values returned by the `ServiceProvider.get_service_choices` method.
    """
    identifier = InternalIdentifierField(unique=True,
                                         verbose_name=_("identifier"))
    enabled = models.BooleanField(default=False, verbose_name=_("enabled"))
    shop = models.ForeignKey(Shop, verbose_name=_("shop"))

    choice_identifier = models.CharField(blank=True,
                                         max_length=64,
                                         verbose_name=_("choice identifier"))

    # These are for migrating old methods to new architecture
    old_module_identifier = models.CharField(max_length=64, blank=True)
    old_module_data = JSONField(blank=True, null=True)

    name = TranslatedField(any_language=True)
    description = TranslatedField()
    logo = FilerImageField(blank=True,
                           null=True,
                           on_delete=models.SET_NULL,
                           verbose_name=_("logo"))
    tax_class = models.ForeignKey('TaxClass',
                                  on_delete=models.PROTECT,
                                  verbose_name=_("tax class"))

    behavior_components = models.ManyToManyField(
        'ServiceBehaviorComponent', verbose_name=_("behavior components"))

    objects = ServiceQuerySet.as_manager()

    class Meta:
        abstract = True

    @property
    def provider(self):
        """
        :rtype: shuup.core.models.ServiceProvider
        """
        return getattr(self, self.provider_attr)

    def get_effective_name(self, source):
        """
        Get effective name of the service for given order source.

        By default, effective name is the same as name of this service,
        but if there is a service provider with a custom implementation
        for `~shuup.core.models.ServiceProvider.get_effective_name`
        method, then this can be different.

        :type source: shuup.core.order_creator.OrderSource
        :rtype: str
        """
        if not self.provider:
            return self.name
        return self.provider.get_effective_name(self, source)

    def is_available_for(self, source):
        """
        Return true if service is available for given source.

        :type source: shuup.core.order_creator.OrderSource
        :rtype: bool
        """
        return not any(self.get_unavailability_reasons(source))

    def get_unavailability_reasons(self, source):
        """
        Get reasons of being unavailable for given source.

        :type source: shuup.core.order_creator.OrderSource
        :rtype: Iterable[ValidationError]
        """
        if not self.provider or not self.provider.enabled or not self.enabled:
            yield ValidationError(_("%s is disabled") % self, code='disabled')

        if source.shop.id != self.shop_id:
            yield ValidationError(_("%s is for different shop") % self,
                                  code='wrong_shop')

        for component in self.behavior_components.all():
            for reason in component.get_unavailability_reasons(self, source):
                yield reason

    def get_total_cost(self, source):
        """
        Get total cost of this service for items in given source.

        :type source: shuup.core.order_creator.OrderSource
        :rtype: PriceInfo
        """
        return _sum_costs(self.get_costs(source), source)

    def get_costs(self, source):
        """
        Get costs of this service for items in given source.

        :type source: shuup.core.order_creator.OrderSource
        :return: description, price and tax class of the costs
        :rtype: Iterable[ServiceCost]
        """
        for component in self.behavior_components.all():
            for cost in component.get_costs(self, source):
                yield cost

    def get_lines(self, source):
        """
        Get lines for given source.

        Lines are created based on costs.  Costs without description are
        combined to single line.

        :type source: shuup.core.order_creator.OrderSource
        :rtype: Iterable[shuup.core.order_creator.SourceLine]
        """
        for (num, line_data) in enumerate(self._get_line_data(source), 1):
            (price_info, tax_class, text) = line_data
            yield self._create_line(source, num, price_info, tax_class, text)

    def _get_line_data(self, source):
        # Split to costs with and without description
        costs_with_description = []
        costs_without_description = []
        for cost in self.get_costs(source):
            if cost.description:
                costs_with_description.append(cost)
            else:
                assert cost.tax_class is None
                costs_without_description.append(cost)

        if not (costs_with_description or costs_without_description):
            costs_without_description = [ServiceCost(source.create_price(0))]

        effective_name = self.get_effective_name(source)

        # Yield the combined cost first
        if costs_without_description:
            combined_price_info = _sum_costs(costs_without_description, source)
            yield (combined_price_info, self.tax_class, effective_name)

        # Then the costs with description, one line for each cost
        for cost in costs_with_description:
            tax_class = (cost.tax_class or self.tax_class)
            text = _('%(service_name)s: %(sub_item)s') % {
                'service_name': effective_name,
                'sub_item': cost.description,
            }
            yield (cost.price_info, tax_class, text)

    def _create_line(self, source, num, price_info, tax_class, text):
        return source.create_line(
            line_id=self._generate_line_id(num),
            type=self.line_type,
            quantity=price_info.quantity,
            text=text,
            base_unit_price=price_info.base_unit_price,
            discount_amount=price_info.discount_amount,
            tax_class=tax_class,
        )

    def _generate_line_id(self, num):
        return "%s-%02d-%08x" % (self.line_type.name.lower(), num,
                                 random.randint(0, 0x7FFFFFFF))

    def _make_sure_is_usable(self):
        if not self.provider:
            raise ValueError('%r has no %s' % (self, self.provider_attr))
        if not self.enabled:
            raise ValueError('%r is disabled' % (self, ))
        if not self.provider.enabled:
            raise ValueError('%s of %r is disabled' %
                             (self.provider_attr, self))
예제 #6
0
class Event(EventModelMixin):
    """
    Hold the information about an event in the calendar.

    :created_by: FK to the ``User``, who created this event.
    :category: FK to the ``EventCategory`` this event belongs to.
    :rule: FK to the definition of the recurrence of an event.
    :end_recurring_period: The possible end of the recurring definition.
    :title: The title of the event.
    :image: Optional image of the event.

    """

    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=_('Created by'),
        related_name='events',
        blank=True,
        null=True,
    )

    category = models.ForeignKey(
        'EventCategory',
        verbose_name=_('Category'),
        related_name='events',
        null=True,
        blank=True,
    )

    rule = models.ForeignKey(
        'Rule',
        verbose_name=_('Rule'),
        blank=True,
        null=True,
    )

    end_recurring_period = models.DateTimeField(
        verbose_name=_('End of recurring'),
        blank=True,
        null=True,
    )

    title = models.CharField(
        max_length=256,
        verbose_name=_('Title'),
    )

    image = FilerImageField(
        verbose_name=_('Image'),
        related_name='calendarium_event_images',
        null=True,
        blank=True,
    )

    objects = EventModelManager()

    def get_absolute_url(self):
        return reverse('calendar_event_detail', kwargs={'pk': self.pk})

    def _create_occurrence(self, occ_start, occ_end=None):
        """Creates an Occurrence instance."""
        # if the length is not altered, it is okay to only pass occ_start
        if not occ_end:
            occ_end = occ_start + (self.end - self.start)
        return Occurrence(
            event=self,
            start=occ_start,
            end=occ_end,
            # TODO not sure why original start and end also are occ_start/_end
            original_start=occ_start,
            original_end=occ_end,
            title=self.title,
            description=self.description,
            creation_date=self.creation_date,
            created_by=self.created_by)

    def _get_date_gen(self, rr, start, end):
        """Returns a generator to create the start dates for occurrences."""
        date = rr.after(start)
        while end and date <= end or not (end):
            yield date
            date = rr.after(date)

    def _get_occurrence_gen(self, start, end):
        """Computes all occurrences for this event from start to end."""
        # get length of the event
        length = self.end - self.start

        if self.rule:
            # if the end of the recurring period is before the end arg passed
            # the end of the recurring period should be the new end
            if self.end_recurring_period and end and (self.end_recurring_period
                                                      < end):
                end = self.end_recurring_period
            # making start date generator
            occ_start_gen = self._get_date_gen(self.get_rrule_object(),
                                               start - length, end)

            # chosing the first item from the generator to initiate
            occ_start = next(occ_start_gen)
            while not end or (end and occ_start <= end):
                occ_end = occ_start + length
                yield self._create_occurrence(occ_start, occ_end)
                occ_start = next(occ_start_gen)
        else:
            # check if event is in the period
            if (not end or self.start < end) and self.end >= start:
                yield self._create_occurrence(self.start, self.end)

    def get_occurrences(self, start, end=None):
        """Returns all occurrences from start to end."""
        # get persistent occurrences
        persistent_occurrences = self.occurrences.all()

        # setup occ_replacer with p_occs
        occ_replacer = OccurrenceReplacer(persistent_occurrences)

        # compute own occurrences according to rule that overlap with the
        # period
        occurrence_gen = self._get_occurrence_gen(start, end)
        # get additional occs, that we need to take into concern
        additional_occs = occ_replacer.get_additional_occurrences(start, end)
        occ = next(occurrence_gen)
        while not end or (occ.start < end or any(additional_occs)):
            if occ_replacer.has_occurrence(occ):
                p_occ = occ_replacer.get_occurrence(occ)

                # if the persistent occ falls into the period, replace it
                if (end and p_occ.start < end) and p_occ.end >= start:
                    estimated_occ = p_occ
            else:
                # if there is no persistent match, use the original occ
                estimated_occ = occ

            if any(additional_occs) and (estimated_occ.start
                                         == additional_occs[0].start):
                final_occ = additional_occs.pop(0)
            else:
                final_occ = estimated_occ
            if not final_occ.cancelled:
                yield final_occ
            occ = next(occurrence_gen)

    def get_parent_category(self):
        """Returns the main category of this event."""
        if self.category.parent:
            return self.category.parent
        return self.category

    def get_rrule_object(self):
        """Returns the rrule object for this ``Event``."""
        if self.rule:
            params = self.rule.get_params()
            frequency = 'rrule.{0}'.format(self.rule.frequency)
            return rrule.rrule(eval(frequency), dtstart=self.start, **params)
예제 #7
0
class VideoPlayer(CMSPlugin):
    """
    Renders either an Iframe when ``link`` is provided or the HTML5 <video> tag
    """
    template = models.CharField(
        verbose_name=_('Template'),
        choices=get_templates(),
        default=get_templates()[0][0],
        max_length=255,
    )
    label = models.CharField(
        verbose_name=_('Label'),
        blank=True,
        max_length=255,
    )
    embed_link = models.CharField(
        verbose_name=_('Embed link'),
        blank=True,
        max_length=255,
        help_text=_(
            'Use this field to embed videos from external services '
            'such as YouTube, Vimeo or others. Leave it blank to upload video '
            'files by adding nested "Source" plugins.'),
    )
    parameters = AttributesField(
        verbose_name=_('Parameters'),
        blank=True,
        help_text=_('Parameters are appended to the video link if provided.'),
    )
    poster = FilerImageField(
        verbose_name=_('Poster'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    attributes = AttributesField(
        verbose_name=_('Attributes'),
        blank=True,
    )

    # Add an app namespace to related_name to avoid field name clashes
    # with any other plugins that have a field with the same name as the
    # lowercase of the class name of this model.
    # https://github.com/divio/django-cms/issues/5030
    cmsplugin_ptr = models.OneToOneField(
        CMSPlugin,
        related_name='%(app_label)s_%(class)s',
        parent_link=True,
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return self.label or self.embed_link or str(self.pk)

    def copy_relations(self, oldinstance):
        # Because we have a ForeignKey, it's required to copy over
        # the reference from the instance to the new plugin.
        self.poster = oldinstance.poster

    @property
    def embed_link_with_parameters(self):
        if not self.embed_link:
            return ''
        if not self.parameters:
            return self.embed_link
        return self._append_url_parameters(self.embed_link, self.parameters)

    def _append_url_parameters(self, url, params):
        url_parts = list(urlparse(url))
        query = dict(parse_qsl(url_parts[4]))
        query.update(params)
        url_parts[4] = urlencode(query)
        return urlunparse(url_parts)
예제 #8
0
class Migration(migrations.Migration):

    dependencies = [
        ('filer', '0006_auto_20160623_1627'),
        ('cms', '0014_auto_20160404_1908'),
    ]

    operations = [
        migrations.CreateModel(
            name='Youtube',
            fields=[
                ('cmsplugin_ptr',
                 models.OneToOneField(
                     parent_link=True,
                     auto_created=True,
                     primary_key=True,
                     serialize=False,
                     to='cms.CMSPlugin',
                     on_delete=django.db.models.deletion.CASCADE)),
                ('title',
                 models.CharField(max_length=150,
                                  verbose_name='Title',
                                  blank=True)),
                ('video_url',
                 models.URLField(
                     help_text='Paste the URL of the YouTube video',
                     verbose_name='Video URL')),
                ('width',
                 models.PositiveIntegerField(
                     help_text=
                     'Sets the width of your player, used on some templates where applicable',
                     null=True,
                     verbose_name='Width',
                     blank=True)),
                ('height',
                 models.PositiveIntegerField(
                     help_text=
                     'Sets the height of your player, used on some templates where applicable',
                     null=True,
                     verbose_name='Height',
                     blank=True)),
                ('description',
                 models.TextField(
                     help_text=
                     'You can add a Description to your video, to be displayed beneath your video on your page.',
                     null=True,
                     verbose_name='Video Description',
                     blank=True)),
                ('description_option',
                 models.CharField(
                     default=settings.DJANGOCMS_YOUTUBE_DESCRIPTION_CHOICES[0]
                     [0],
                     max_length=50,
                     verbose_name='Description Option',
                     blank=True,
                     choices=settings.DJANGOCMS_YOUTUBE_DESCRIPTION_CHOICES)),
                ('theme',
                 models.CharField(
                     default=settings.DJANGOCMS_YOUTUBE_DEFAULT_THEME,
                     max_length=100,
                     verbose_name='Theme',
                     choices=settings.DJANGOCMS_YOUTUBE_THEME_CHOICES)),
                ('plugin_template',
                 models.CharField(
                     default=settings.DJANGOCMS_YOUTUBE_TEMPLATES[0][0],
                     max_length=255,
                     verbose_name='Template',
                     choices=settings.DJANGOCMS_YOUTUBE_TEMPLATES)),
                ('video_data',
                 JSONField(
                     help_text=
                     'For advanced users only \u2014 please do not edit this data unless you know what you are doing.',
                     null=True,
                     verbose_name='YouTube Data',
                     blank=True)),
                ('thumbnail',
                 FilerImageField(
                     related_name='djangocms_youtube_thumbnails',
                     on_delete=django.db.models.deletion.SET_NULL,
                     blank=True,
                     to='filer.Image',
                     help_text=
                     'Image Overlay - this image will display over the video on your site and allow users to see an image of your choice before playing the video.',
                     null=True,
                     verbose_name='Custom Thumbnail')),
            ],
            options={
                'abstract': False,
            },
            bases=('cms.cmsplugin', ),
        ),
    ]
예제 #9
0
class CategorizationModel(TranslatableModelMixin, MPTTModel):
    """
    Categorization model, adds translated fields and uses django-mptt.
    Intended for use like this:
        class Category(CategorizationModel):
            translations = _categorization_translated_fields()
    """
    _featured_image = FilerImageField(
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_('Featured image'),
        help_text=_(
            "If left empty for childs, a parent's featured image will be used."
        ))

    parent = TreeForeignKey('self',
                            models.CASCADE,
                            blank=True,
                            null=True,
                            related_name='children',
                            verbose_name=_('Parent'))

    modifiers = models.ManyToManyField(
        Modifier,
        blank=True,
        verbose_name=_('Modifiers'),
        limit_choices_to={'kind__in': [Modifier.STANDARD, Modifier.DISCOUNT]})

    flags = models.ManyToManyField(
        Flag,
        blank=True,
        verbose_name=_('Flags'),
        help_text=_('Check flags for products in this categorization.'))

    active = models.BooleanField(
        _('Active'),
        default=True,
        help_text=_('Is this categorization publicly visible.'))
    created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
    updated_at = models.DateTimeField(_('Updated at'), auto_now=True)

    objects = CategorizationManager()

    class Meta:
        abstract = True

    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()

        url_name = 'shopit-%s-detail' % self._meta.model.__name__.lower()

        with switch_language(self, language):
            try:
                return reverse(url_name, args=[self.get_path()])
            except NoReverseMatch:
                pass

    def get_path(self):
        """
        Returns ful url path for categorization object.
        """
        path = []
        for obj in self.get_ancestors(include_self=True):
            path.append(obj.safe_translation_getter('slug', ''))
        return '/'.join(path)

    @property
    def featured_image(self):
        if not self._featured_image and self.is_child_node():
            return self.parent.featured_image
        return self._featured_image

    @featured_image.setter
    def featured_image(self, value):
        self._featured_image = value

    def get_products(self):
        """
        Returns active products from this categorization
        """
        products = getattr(self, '_products', None)
        if products is None:
            products = self.product_set.active()
            for child in self.get_descendants():
                products = products | child.product_set.active()
            setattr(self, '_products', products)
        return products

    def get_modifiers(self, distinct=True):
        """
        Returns all modifiers for the current tree.
        """
        mods = getattr(self, '_mods', None)
        if mods is None:
            mods = self.modifiers.active()
            for parent in self.get_ancestors():
                mods = mods | parent.modifiers.active()
            setattr(self, '_mods', mods)
        return mods.distinct() if distinct else mods

    def get_flags(self, distinct=True):
        """
        Returns all flags for the current tree.
        """
        flags = getattr(self, '_flags', None)
        if flags is None:
            flags = self.flags.active()
            for parent in self.get_ancestors():
                flags = flags | parent.flags.active()
            setattr(self, '_flags', flags)
        return flags.distinct() if distinct else flags
예제 #10
0
class Post(TranslatableModel):
    """
    Post
    """
    DRAFT = 0  # Post is visible to staff
    PRIVATE = 1  # Post is visible to author only
    PUBLIC = 2  # Post is public
    HIDDEN = 3  # Post is hidden from everybody

    STATUS_CODES = (
        (DRAFT, _('Draft')),
        (PRIVATE, _('Private')),
        (PUBLIC, _('Public')),
        (HIDDEN, _('Hidden')),
    )

    date_added = models.DateTimeField(_('Date added'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'), auto_now=True)

    status = models.IntegerField(
        _('Status'),
        choices=STATUS_CODES,
        default=DRAFT,
        help_text=
        _('When draft post is visible to staff only, when private to author only, and when public to everyone.'
          ))

    date_published = models.DateTimeField(_('Published on'),
                                          default=timezone.now)
    category = TreeForeignKey(Category,
                              models.SET_NULL,
                              blank=True,
                              null=True,
                              verbose_name=_('Category'))
    tags = models.ManyToManyField(Tag,
                                  blank=True,
                                  related_name='tagged_posts',
                                  verbose_name=_('Tags'))
    author = models.ForeignKey(USER_MODEL,
                               models.SET_NULL,
                               blank=True,
                               null=True,
                               verbose_name=_('Author'))

    featured_image = FilerImageField(
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_('Featured Image'),
    )

    translations = TranslatedFields(
        title=models.CharField(_('Title'), max_length=255),
        slug=models.SlugField(_('Slug'), db_index=True),
        description=models.TextField(_('Description'), blank=True),
        meta_title=models.CharField(_('Meta title'),
                                    max_length=255,
                                    blank=True),
        meta_description=models.TextField(
            _('Meta description'),
            max_length=155,
            blank=True,
            help_text=_('The text displayed in search engines.')),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    body = PlaceholderField('blogit_post_body', related_name='post_body_set')

    objects = PostManager()

    class Meta:
        db_table = 'blogit_posts'
        verbose_name = _('Post')
        verbose_name_plural = _('Posts')
        ordering = ('-date_published', )
        get_latest_by = 'date_published'

    def __str__(self):
        return self.name

    def get_absolute_url(self, language=None):
        if not language:
            language = get_current_language()

        with switch_language(self, language):
            if bs.POST_DETAIL_DATE_URL:
                return reverse('blogit_post_detail_date',
                               kwargs={
                                   'year': self.date_published.year,
                                   'month': self.date_published.month,
                                   'day': self.date_published.day,
                                   'slug':
                                   self.safe_translation_getter('slug'),
                               })

            return reverse(
                'blogit_post_detail',
                kwargs={'slug': self.safe_translation_getter('slug')})

    def get_search_data(self, language=None, request=None):
        """
        Returns search text data for current object
        """
        if not self.pk:
            return ''

        bits = [self.name]
        description = self.safe_translation_getter('description')
        if description:
            bits.append(force_unicode(strip_tags(description)))

        if self.category:
            bits.append(self.category.safe_translation_getter('name'))
            description = self.category.safe_translation_getter('description')
            if description:
                bits.append(force_unicode(strip_tags(description)))

        for tag in self.tags.all():
            bits.append(tag.safe_translation_getter('name'))
            description = tag.safe_translation_getter('description', '')
            if description:
                bits.append(force_unicode(strip_tags(description)))

        bits.append(get_text_from_placeholder(self.body, language, request))
        return ' '.join(bits).strip()

    def get_meta_title(self):
        return self.safe_translation_getter('meta_title') or self.name

    def get_meta_description(self):
        return self.safe_translation_getter(
            'meta_description') or self.safe_translation_getter('description')

    @property
    def name(self):
        return self.safe_translation_getter('title', any_language=True)

    @property
    def is_published(self):
        return self.status == self.PUBLIC and self.date_published <= timezone.now(
        )

    @property
    def previous_post(self):
        return self.previous_next_posts[0]

    @property
    def next_post(self):
        return self.previous_next_posts[1]

    @property
    def previous_next_posts(self):
        previous_next = getattr(self, 'previous_next', None)

        if previous_next is None:
            if not self.is_published:
                previous_next = (None, None)
                setattr(self, 'previous_next', previous_next)
                return previous_next

            posts = list(Post.objects.public().published())
            index = posts.index(self)

            try:
                previous = posts[index + 1]
            except IndexError:
                previous = None

            if index:
                next = posts[index - 1]
            else:
                next = None
            previous_next = (previous, next)
            setattr(self, 'previous_next', previous_next)
        return previous_next
예제 #11
0
class Shop(ChangeProtected, TranslatableShuupModel):
    protected_fields = ["currency", "prices_include_tax"]
    change_protect_message = _(
        "The following fields cannot be changed since there are existing orders for this shop"
    )

    created_on = models.DateTimeField(auto_now_add=True,
                                      editable=False,
                                      verbose_name=_('created on'))
    modified_on = models.DateTimeField(auto_now=True,
                                       editable=False,
                                       verbose_name=_('modified on'))
    identifier = InternalIdentifierField(unique=True, max_length=128)
    domain = models.CharField(
        max_length=128,
        blank=True,
        null=True,
        unique=True,
        verbose_name=_("domain"),
        help_text=
        _("Your shop domain name. Use this field to configure the URL that is used to visit your site. "
          "Note: this requires additional configuration through your internet domain registrar."
          ))
    status = EnumIntegerField(
        ShopStatus,
        default=ShopStatus.DISABLED,
        verbose_name=_("status"),
        help_text=_(
            "Your shop status. Disable your shop if it is no longer in use."))
    owner = models.ForeignKey("Contact",
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL,
                              verbose_name=_("contact"))
    options = JSONField(blank=True, null=True, verbose_name=_("options"))
    currency = CurrencyField(
        default=_get_default_currency,
        verbose_name=_("currency"),
        help_text=
        _("The primary shop currency. This is the currency used when selling your products."
          ))
    prices_include_tax = models.BooleanField(
        default=True,
        verbose_name=_("prices include tax"),
        help_text=
        _("This option defines whether product prices entered in admin include taxes. "
          "Note this behavior can be overridden with contact group pricing."))
    logo = FilerImageField(verbose_name=_("logo"),
                           blank=True,
                           null=True,
                           on_delete=models.SET_NULL,
                           help_text=_("Shop logo. Will be shown at theme."),
                           related_name="shop_logos")

    favicon = FilerImageField(
        verbose_name=_("favicon"),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        help_text=_(
            "Shop favicon. Will be shown next to the address on browser."),
        related_name="shop_favicons")

    maintenance_mode = models.BooleanField(
        verbose_name=_("maintenance mode"),
        default=False,
        help_text=
        _("Check this if you would like to make your shop temporarily unavailable while you do some shop maintenance."
          ))
    contact_address = models.ForeignKey("MutableAddress",
                                        verbose_name=_("contact address"),
                                        blank=True,
                                        null=True,
                                        on_delete=models.SET_NULL)
    staff_members = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                           blank=True,
                                           related_name="+",
                                           verbose_name=_('staff members'))

    translations = TranslatedFields(
        name=models.CharField(
            max_length=64,
            verbose_name=_("name"),
            help_text=_(
                "The shop name. This name is displayed throughout admin.")),
        public_name=models.CharField(
            max_length=64,
            verbose_name=_("public name"),
            help_text=
            _("The public shop name. This name is displayed in the store front and in any customer email correspondence."
              )),
        maintenance_message=models.CharField(
            max_length=300,
            blank=True,
            verbose_name=_("maintenance message"),
            help_text=
            _("The message to display to customers while your shop is in maintenance mode."
              )))

    def __str__(self):
        return self.safe_translation_getter("name",
                                            default="Shop %d" % self.pk)

    def create_price(self, value):
        """
        Create a price with given value and settings of this shop.

        Takes the ``prices_include_tax`` and ``currency`` settings of
        this Shop into account.

        :type value: decimal.Decimal|int|str
        :rtype: shuup.core.pricing.Price
        """
        if self.prices_include_tax:
            return TaxfulPrice(value, self.currency)
        else:
            return TaxlessPrice(value, self.currency)

    def _are_changes_protected(self):
        return Order.objects.filter(shop=self).exists()
예제 #12
0
class Person(TranslationHelperMixin, TranslatedAutoSlugifyMixin,
             TranslatableModel):
    slug_source_field_name = 'name'

    translations = TranslatedFields(
        name=models.CharField(_('name'),
                              max_length=255,
                              blank=False,
                              default='',
                              help_text=_("Provide this person's name.")),
        slug=models.SlugField(
            _('unique slug'),
            max_length=255,
            blank=True,
            default='',
            help_text=_("Leave blank to auto-generate a unique slug.")),
        function=models.CharField(_('role'),
                                  max_length=255,
                                  blank=True,
                                  default=''),
        description=HTMLField(_('description'), blank=True, default=''))
    phone = models.CharField(verbose_name=_('phone'),
                             null=True,
                             blank=True,
                             max_length=100)
    mobile = models.CharField(verbose_name=_('mobile'),
                              null=True,
                              blank=True,
                              max_length=100)
    fax = models.CharField(verbose_name=_('fax'),
                           null=True,
                           blank=True,
                           max_length=100)
    email = models.EmailField(verbose_name=_("email"), blank=True, default='')
    website = models.URLField(verbose_name=_('website'), null=True, blank=True)
    groups = SortedM2MModelField(
        'aldryn_people.Group',
        default=None,
        blank=True,
        related_name='people',
        help_text=_('Choose and order the groups for this person, the first '
                    'will be the "primary group".'))
    visual = FilerImageField(null=True,
                             blank=True,
                             default=None,
                             on_delete=models.SET_NULL)
    vcard_enabled = models.BooleanField(
        verbose_name=_('enable vCard download'), default=True)
    user = models.OneToOneField(getattr(settings, 'AUTH_USER_MODEL',
                                        'auth.User'),
                                null=True,
                                blank=True,
                                related_name='persons',
                                on_delete=models.CASCADE)

    class Meta:
        verbose_name = _('Person')
        verbose_name_plural = _('People')

    def __str__(self):
        pkstr = str(self.pk)

        if six.PY2:
            pkstr = six.u(pkstr)
        name = self.safe_translation_getter('name',
                                            default='',
                                            any_language=True).strip()
        return name if len(name) > 0 else pkstr

    @property
    def primary_group(self):
        """Simply returns the first in `groups`, if any, else None."""
        return self.groups.first()

    @property
    def comment(self):
        return self.safe_translation_getter('description', '')

    def get_absolute_url(self, language=None):
        if not language:
            language = get_current_language()
        slug, language = self.known_translation_getter('slug',
                                                       None,
                                                       language_code=language)
        if slug:
            kwargs = {'slug': slug}
        else:
            kwargs = {'pk': self.pk}
        with override(language):
            # do not fail with 500 error so that if detail view can't be
            # resolved we still can use plugins.
            try:
                url = reverse('aldryn_people:person-detail', kwargs=kwargs)
            except NoReverseMatch:
                url = ''
        return url

    def get_vcard_url(self, language=None):
        if not language:
            language = get_current_language()
        slug = self.safe_translation_getter('slug',
                                            None,
                                            language_code=language,
                                            any_language=False)
        if slug:
            kwargs = {'slug': slug}
        else:
            kwargs = {'pk': self.pk}
        with override(language):
            return reverse('aldryn_people:download_vcard', kwargs=kwargs)

    def get_vcard(self, request=None):
        vcard = Vcard()
        function = self.safe_translation_getter('function')

        safe_name = self.safe_translation_getter('name',
                                                 default="Person: {0}".format(
                                                     self.pk))
        vcard.add_line('FN', safe_name)
        vcard.add_line('N', [None, safe_name, None, None, None])

        if self.visual:
            ext = self.visual.extension.upper()
            try:
                with open(self.visual.path, 'rb') as f:
                    data = force_text(base64.b64encode(f.read()))
                    vcard.add_line('PHOTO', data, TYPE=ext, ENCODING='b')
            except IOError:
                if request:
                    url = urlparse.urljoin(request.build_absolute_uri(),
                                           self.visual.url),
                    vcard.add_line('PHOTO', url, TYPE=ext)

        if self.email:
            vcard.add_line('EMAIL', self.email)

        if function:
            vcard.add_line('TITLE', self.function)

        if self.phone:
            vcard.add_line('TEL', self.phone, TYPE='WORK')
        if self.mobile:
            vcard.add_line('TEL', self.mobile, TYPE='CELL')

        if self.fax:
            vcard.add_line('TEL', self.fax, TYPE='FAX')
        if self.website:
            vcard.add_line('URL', self.website)

        if self.primary_group:
            group_name = self.primary_group.safe_translation_getter(
                'name', default="Group: {0}".format(self.primary_group.pk))
            if group_name:
                vcard.add_line('ORG', group_name)
            if self.primary_group.address or self.primary_group.city or self.primary_group.postal_code:
                vcard.add_line('ADR', (
                    None,
                    None,
                    self.primary_group.address,
                    self.primary_group.city,
                    None,
                    self.primary_group.postal_code,
                    None,
                ),
                               TYPE='WORK')

            if self.primary_group.phone:
                vcard.add_line('TEL', self.primary_group.phone, TYPE='WORK')
            if self.primary_group.fax:
                vcard.add_line('TEL', self.primary_group.fax, TYPE='FAX')
            if self.primary_group.website:
                vcard.add_line('URL', self.primary_group.website)

        return six.b('{}'.format(vcard))
class FilerVideo(CMSPlugin):
    # player settings
    movie = FilerFileField(
        verbose_name=_('movie file'),
        help_text=_('use .flv file or h264 encoded video file'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
    )
    movie_url = models.CharField(
        _('movie url'),
        max_length=255,
        help_text=
        _('vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-kpo'
          ),
        blank=True,
        null=True)
    image = FilerImageField(
        verbose_name=_('image'),
        help_text=_('preview image file'),
        null=True,
        blank=True,
        related_name='filer_video_image',
        on_delete=models.SET_NULL,
    )

    width = models.PositiveSmallIntegerField(_('width'),
                                             default=settings.VIDEO_WIDTH)
    height = models.PositiveSmallIntegerField(_('height'),
                                              default=settings.VIDEO_HEIGHT)

    auto_play = models.BooleanField(_('auto play'),
                                    default=settings.VIDEO_AUTOPLAY)
    auto_hide = models.BooleanField(_('auto hide'),
                                    default=settings.VIDEO_AUTOHIDE)
    fullscreen = models.BooleanField(_('fullscreen'),
                                     default=settings.VIDEO_FULLSCREEN)
    loop = models.BooleanField(_('loop'), default=settings.VIDEO_LOOP)

    # plugin settings
    bgcolor = models.CharField(_('background color'),
                               max_length=6,
                               default=settings.VIDEO_BG_COLOR,
                               help_text=_('Hexadecimal, eg ff00cc'))
    textcolor = models.CharField(_('text color'),
                                 max_length=6,
                                 default=settings.VIDEO_TEXT_COLOR,
                                 help_text=_('Hexadecimal, eg ff00cc'))
    seekbarcolor = models.CharField(_('seekbar color'),
                                    max_length=6,
                                    default=settings.VIDEO_SEEKBAR_COLOR,
                                    help_text=_('Hexadecimal, eg ff00cc'))
    seekbarbgcolor = models.CharField(_('seekbar bg color'),
                                      max_length=6,
                                      default=settings.VIDEO_SEEKBARBG_COLOR,
                                      help_text=_('Hexadecimal, eg ff00cc'))
    loadingbarcolor = models.CharField(_('loadingbar color'),
                                       max_length=6,
                                       default=settings.VIDEO_LOADINGBAR_COLOR,
                                       help_text=_('Hexadecimal, eg ff00cc'))
    buttonoutcolor = models.CharField(_('button out color'),
                                      max_length=6,
                                      default=settings.VIDEO_BUTTON_OUT_COLOR,
                                      help_text=_('Hexadecimal, eg ff00cc'))
    buttonovercolor = models.CharField(
        _('button over color'),
        max_length=6,
        default=settings.VIDEO_BUTTON_OVER_COLOR,
        help_text=_('Hexadecimal, eg ff00cc'))
    buttonhighlightcolor = models.CharField(
        _('button highlight color'),
        max_length=6,
        default=settings.VIDEO_BUTTON_HIGHLIGHT_COLOR,
        help_text=_('Hexadecimal, eg ff00cc'))
    cmsplugin_ptr = models.OneToOneField(
        to=CMSPlugin,
        related_name='%(app_label)s_%(class)s',
        parent_link=True,
    )

    def __str__(self):
        if self.movie:
            name = self.movie.path
        else:
            name = self.movie_url
        return "%s" % basename(name)

    def get_height(self):
        return "%s" % (self.height)

    def get_width(self):
        return "%s" % (self.width)

    def get_movie(self):
        if self.movie:
            return self.movie.url
        else:
            return self.movie_url
예제 #14
0
class TextNGVariableFilerImage(TextNGVariableBase):
    value = FilerImageField(null=True, blank=True, verbose_name=_('value'))

    class Meta:
        verbose_name = _('image')
        verbose_name_plural = _('images')
예제 #15
0
class FilerImage(CMSPlugin):
    LEFT = "left"
    RIGHT = "right"
    CENTER = "center"
    FLOAT_CHOICES = (
        (LEFT, _("left")),
        (RIGHT, _("right")),
        (CENTER, _("center")),
    )
    STYLE_CHOICES = settings.CMSPLUGIN_FILER_IMAGE_STYLE_CHOICES
    DEFAULT_STYLE = settings.CMSPLUGIN_FILER_IMAGE_DEFAULT_STYLE
    EXCLUDED_KEYS = [
        'class',
        'href',
        'target',
    ]

    style = models.CharField(_('Style'),
                             choices=STYLE_CHOICES,
                             default=DEFAULT_STYLE,
                             max_length=50,
                             blank=True)
    caption_text = models.CharField(_("caption text"),
                                    null=True,
                                    blank=True,
                                    max_length=255)
    image = FilerImageField(
        null=True,
        blank=True,
        default=None,
        verbose_name=_("image"),
        on_delete=models.SET_NULL,
    )
    image_url = models.URLField(_("alternative image url"),
                                null=True,
                                blank=True,
                                default=None)
    alt_text = models.CharField(_("alt text"),
                                null=True,
                                blank=True,
                                max_length=255)
    use_original_image = models.BooleanField(
        _("use the original image"),
        default=False,
        help_text=_(
            'do not resize the image. use the original image instead.'))
    thumbnail_option = models.ForeignKey(
        'filer.ThumbnailOption',
        null=True,
        blank=True,
        verbose_name=_("thumbnail option"),
        help_text=
        _('overrides width, height, crop and upscale with values from the selected thumbnail option'
          ))
    use_autoscale = models.BooleanField(
        _("use automatic scaling"),
        default=False,
        help_text=_(
            'tries to auto scale the image based on the placeholder context'))
    width = models.PositiveIntegerField(_("width"), null=True, blank=True)
    height = models.PositiveIntegerField(_("height"), null=True, blank=True)
    crop = models.BooleanField(_("crop"), default=True)
    upscale = models.BooleanField(_("upscale"), default=True)
    alignment = models.CharField(_("image alignment"),
                                 max_length=10,
                                 blank=True,
                                 null=True,
                                 choices=FLOAT_CHOICES)

    free_link = models.CharField(
        _("link"),
        max_length=2000,
        blank=True,
        null=True,
        help_text=_("if present image will be clickable"))
    page_link = PageField(null=True,
                          blank=True,
                          help_text=_("if present image will be clickable"),
                          verbose_name=_("page link"))
    file_link = FilerFileField(
        null=True,
        blank=True,
        default=None,
        verbose_name=_("file link"),
        help_text=_("if present image will be clickable"),
        related_name='+',
        on_delete=models.SET_NULL,
    )
    original_link = models.BooleanField(
        _("link original image"),
        default=False,
        help_text=_("if present image will be clickable"))
    description = models.TextField(_("description"), blank=True, null=True)
    target_blank = models.BooleanField(_('Open link in new window'),
                                       default=False)
    link_attributes = AttributesField(
        excluded_keys=EXCLUDED_KEYS,
        blank=True,
        help_text=_('Optional. Adds HTML attributes to the rendered link.'))
    cmsplugin_ptr = models.OneToOneField(
        to=CMSPlugin,
        related_name='%(app_label)s_%(class)s',
        parent_link=True,
    )

    # we only add the image to select_related. page_link and file_link are FKs
    # as well, but they are not used often enough to warrant the impact of two
    # additional LEFT OUTER JOINs.
    objects = FilerPluginManager(select_related=('image', ))

    class Meta:
        verbose_name = _("filer image")
        verbose_name_plural = _("filer images")

    def clean(self):
        from django.core.exceptions import ValidationError
        # Make sure that either image or image_url is set
        if (not self.image and not self.image_url) or (self.image
                                                       and self.image_url):
            raise ValidationError(
                _('Either an image or an image url must be selected.'))

    def __str__(self):
        if self.image:
            return self.image.label
        else:
            return _("Image Publication %(caption)s") % {
                'caption': self.caption or self.alt
            }
        return ''

    @property
    def caption(self):
        if self.image:
            return self.caption_text or self.image.default_caption
        else:
            return self.caption_text

    @property
    def alt(self):
        if self.image:
            return self.alt_text or self.image.default_alt_text or self.image.label
        else:
            return self.alt_text

    @property
    def link(self):
        if self.free_link:
            return self.free_link
        elif self.page_link:
            return self.page_link.get_absolute_url()
        elif self.file_link:
            return self.file_link.url
        elif self.original_link:
            if self.image:
                return self.image.url
            else:
                return self.image_url
        else:
            return ''
예제 #16
0
파일: _contacts.py 프로젝트: wsmoyer/shuup
class Contact(PolymorphicShuupModel):
    is_anonymous = False
    is_all_seeing = False
    default_tax_group_getter = None
    default_contact_group_identifier = None
    default_contact_group_name = None

    created_on = models.DateTimeField(auto_now_add=True,
                                      editable=False,
                                      verbose_name=_('created on'))
    modified_on = models.DateTimeField(auto_now=True,
                                       editable=False,
                                       db_index=True,
                                       null=True,
                                       verbose_name=_('modified on'))
    identifier = InternalIdentifierField(unique=True, null=True, blank=True)
    is_active = models.BooleanField(
        default=True,
        db_index=True,
        verbose_name=_('active'),
        help_text=_("Check this if the contact is an active customer."))
    shops = models.ManyToManyField(
        "shuup.Shop",
        blank=True,
        verbose_name=_('shops'),
        help_text=_("Inform which shops have access to this contact."))

    registration_shop = models.ForeignKey("Shop",
                                          related_name="registrations",
                                          verbose_name=_("registration shop"),
                                          null=True)

    # TODO: parent contact?
    default_shipping_address = models.ForeignKey(
        "MutableAddress",
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_('shipping address'),
        on_delete=models.PROTECT)
    default_billing_address = models.ForeignKey(
        "MutableAddress",
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_('billing address'),
        on_delete=models.PROTECT)
    default_shipping_method = models.ForeignKey(
        "ShippingMethod",
        verbose_name=_('default shipping method'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL)
    default_payment_method = models.ForeignKey(
        "PaymentMethod",
        verbose_name=_('default payment method'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL)

    _language = LanguageField(
        verbose_name=_('language'),
        blank=True,
        help_text=
        _("The primary language to be used in all communications with the contact."
          ))
    marketing_permission = models.BooleanField(
        default=False,
        verbose_name=_('marketing permission'),
        help_text=
        _("Check this if the contact can receive marketing and promotional materials."
          ))
    phone = models.CharField(
        max_length=64,
        blank=True,
        verbose_name=_('phone'),
        help_text=_("The primary phone number of the contact."))
    www = models.URLField(
        max_length=128,
        blank=True,
        verbose_name=_('web address'),
        help_text=_("The web address of the contact, if any."))
    timezone = TimeZoneField(
        blank=True,
        null=True,
        verbose_name=_('time zone'),
        help_text=_(
            "The timezone in which the contact resides. This can be used to target the delivery of promotional materials "
            "at a particular time."))
    prefix = models.CharField(
        verbose_name=_('name prefix'),
        max_length=64,
        blank=True,
        help_text=_(
            "The name prefix of the contact. For example, Mr, Mrs, Dr, etc."))
    name = models.CharField(max_length=256,
                            verbose_name=_('name'),
                            help_text=_("The contact name"))
    suffix = models.CharField(
        verbose_name=_('name suffix'),
        max_length=64,
        blank=True,
        help_text=_(
            "The name suffix of the contact. For example, Sr, Jr, etc."))
    name_ext = models.CharField(max_length=256,
                                blank=True,
                                verbose_name=_('name extension'))
    email = models.EmailField(
        max_length=256,
        blank=True,
        verbose_name=_('email'),
        help_text=
        _("The email that will receive order confirmations and promotional materials (if permitted)."
          ))
    tax_group = models.ForeignKey(
        "CustomerTaxGroup",
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        verbose_name=_('tax group'),
        help_text=
        _("Select the contact tax group to use for this contact. "
          "Tax groups can be used to customize the tax rules the that apply to any of this contacts orders. "
          "Tax groups are defined in Settings - Customer Tax Groups and can be applied to tax rules in "
          "Settings - Tax Rules"))
    merchant_notes = models.TextField(
        blank=True,
        verbose_name=_('merchant notes'),
        help_text=
        _("Enter any private notes for this customer that are only accessible in Shuup admin."
          ))
    account_manager = models.ForeignKey("PersonContact",
                                        blank=True,
                                        null=True,
                                        verbose_name=_('account manager'))
    options = PolymorphicJSONField(blank=True,
                                   null=True,
                                   verbose_name=_("options"))
    picture = FilerImageField(
        verbose_name=_("picture"),
        blank=True,
        null=True,
        related_name="picture",
        on_delete=models.SET_NULL,
        help_text=
        _("Contact picture. Can be used alongside contact profile, reviews and messages for example."
          ))

    def __str__(self):
        return self.full_name

    class Meta:
        verbose_name = _('contact')
        verbose_name_plural = _('contacts')

    def __init__(self, *args, **kwargs):
        if self.default_tax_group_getter:
            kwargs.setdefault("tax_group", self.default_tax_group_getter())
        super(Contact, self).__init__(*args, **kwargs)

    @property
    def full_name(self):
        return (" ".join([self.prefix, self.name, self.suffix])).strip()

    @property
    def language(self):
        if self._language is not None:
            return self._language
        return configuration.get(None, "default_contact_language",
                                 settings.LANGUAGE_CODE)

    @language.setter
    def language(self, value):
        self._language = value

    def save(self, *args, **kwargs):
        add_to_default_group = bool(self.pk is None
                                    and self.default_contact_group_identifier)
        super(Contact, self).save(*args, **kwargs)
        if add_to_default_group:
            self.groups.add(self.get_default_group())

    def get_price_display_options(self, **kwargs):
        """
        Get price display options of the contact.

        If the default group (`get_default_group`) defines price display
        options and the contact is member of it, return it.

        If contact is not (anymore) member of the default group or the
        default group does not define options, return one of the groups
        which defines options.  If there is more than one such groups,
        it is undefined which options will be used.

        If contact is not a member of any group that defines price
        display options, return default constructed
        `PriceDisplayOptions`.

        Subclasses may still override this default behavior.

        :rtype: PriceDisplayOptions
        """
        group = kwargs.get("group", None)
        shop = kwargs.get("shop", None)
        if not group:
            groups_with_options = self.groups.with_price_display_options(shop)
            if groups_with_options:
                default_group = self.get_default_group()
                if groups_with_options.filter(pk=default_group.pk).exists():
                    group = default_group
                else:
                    # Contact was removed from the default group.
                    group = groups_with_options.first()

        if not group:
            group = self.get_default_group()

        return get_price_display_options_for_group_and_shop(group, shop)

    @classmethod
    def get_default_group(cls):
        """
        Get or create default contact group for the class.

        Identifier of the group is specified by the class property
        `default_contact_group_identifier`.

        If new group is created, its name is set to value of
        `default_contact_group_name` class property.

        :rtype: core.models.ContactGroup
        """
        obj, created = ContactGroup.objects.get_or_create(
            identifier=cls.default_contact_group_identifier,
            defaults={"name": cls.default_contact_group_name})
        return obj

    def add_to_shops(self, registration_shop, shops):
        """
        Add contact to multiple shops

        :param registration_shop: Shop where contact registers
        :type registration_shop: core.models.Shop
        :param shops: A list of shops
        :type shops: list
        :return:
        """
        # set `registration_shop` first to ensure it's being
        # used if not already set
        for shop in [registration_shop] + shops:
            self.add_to_shop(shop)

    def add_to_shop(self, shop):
        self.shops.add(shop)
        if not self.registration_shop:
            self.registration_shop = shop
            self.save()

    def registered_in(self, shop):
        return (self.registration_shop == shop)

    def in_shop(self, shop, only_registration=False):
        if only_registration:
            return self.registered_in(shop)
        if self.shops.filter(pk=shop.pk).exists():
            return True
        return self.registered_in(shop)
예제 #17
0
class ServiceProvider(PolymorphicTranslatableShuupModel):
    """
    Entity that provides services.

    Good examples of service providers are `Carrier` and
    `PaymentProcessor`.

    When subclassing `ServiceProvider`, set value for `service_model`
    class attribute. It should be a model class, which is a subclass of
    `Service`.
    """
    identifier = InternalIdentifierField(unique=True)
    enabled = models.BooleanField(
        default=True,
        verbose_name=_("enabled"),
        help_text=
        _("Enable this if this service provider can be used when placing orders."
          ))
    name = TranslatedField(any_language=True)
    logo = FilerImageField(blank=True,
                           null=True,
                           on_delete=models.SET_NULL,
                           verbose_name=_("logo"))

    base_translations = TranslatedFields(name=models.CharField(
        max_length=100,
        verbose_name=_("name"),
        help_text=_("The service provider name.")), )

    shops = models.ManyToManyField(
        "shuup.Shop",
        verbose_name=_("shops"),
        related_name="service_providers",
        help_text=
        _("This service provider will be available only for order sources of the given shop. "
          "If blank, this service provider is available for any order source."
          ),
        blank=True)
    supplier = models.ForeignKey(
        "shuup.Supplier",
        on_delete=models.CASCADE,
        verbose_name=_("supplier"),
        related_name="service_providers",
        help_text=
        _("This service provider will be available only for order sources that contain "
          "all items from the configured supplier. If blank, this service provider is "
          "available for any order source."),
        blank=True,
        null=True)

    #: Model class of the provided services (subclass of `Service`)
    service_model = None

    def get_service_choices(self):
        """
        Get all service choices of this provider.

        Subclasses should implement this method.

        :rtype: list[ServiceChoice]
        """
        raise NotImplementedError

    def create_service(self, choice_identifier, **kwargs):
        """
        Create a service for a given choice identifier.

        Subclass implementation may attach some `behavior components
        <ServiceBehaviorComponent>` to the created service.

        Subclasses should provide implementation for `_create_service`
        or override it. Base class implementation calls the
        `_create_service` method with resolved `choice_identifier`.

        :type choice_identifier: str|None
        :param choice_identifier:
          Identifier of the service choice to use.  If None, use the
          default service choice.
        :rtype: shuup.core.models.Service
        """
        if choice_identifier is None:
            choice_identifier = self.get_service_choices()[0].identifier
        return self._create_service(choice_identifier, **kwargs)

    def _create_service(self, choice_identifier, **kwargs):
        """
        Create a service for a given choice identifier.

        :type choice_identifier: str
        :rtype: shuup.core.models.Service
        """
        raise NotImplementedError

    def get_effective_name(self, service, source):
        """
        Get effective name of the service for a given order source.

        Base class implementation will just return name of the given
        service, but that may be changed in a subclass.

        :type service: shuup.core.models.Service
        :type source: shuup.core.order_creator.OrderSource
        :rtype: str
        """
        return service.name
예제 #18
0
 class FilerCssBackground(CssBackgroundAbstractBase):
     '''
     A CSS Background definition plugin, adapted for django-filer.
     '''
     image = FilerImageField()
예제 #19
0
class Person(Publishable):
    """Describes a person"""
    identifier = models.UUIDField(default=uuid.uuid4,
                                  unique=True,
                                  editable=False)
    # Name info
    given_name = models.CharField(
        'First name',
        max_length=100,
        help_text="A person's given or 'first' name(s).")
    additional_name = models.CharField(
        'Middle name',
        blank=True,
        max_length=100,
        help_text="An additional name for a person, \
                                                                              such as a 'middle' name."
    )
    family_name = models.CharField(
        'Last name',
        blank=True,
        max_length=140,
        help_text="A person's family or 'last' name(s).")

    image = FilerImageField(blank=True,
                            null=True,
                            help_text='Preferably a portrait/"head shot".')

    # Biographical Info
    description = MarkdownField('Bio/Description', blank=True)
    description_rendered = models.TextField(blank=True, editable=False)
    citizenships = models.ManyToManyField('locations.Country', blank=True)

    birth_year = models.PositiveSmallIntegerField(blank=True, null=True)
    birth_month = models.PositiveSmallIntegerField(blank=True, null=True)
    birth_day = models.PositiveSmallIntegerField(blank=True, null=True)

    # Notes
    notes = MarkdownField(blank=True)

    # Relations
    events = models.ManyToManyField('Event', blank=True)

    class Meta:
        ordering = ['family_name', 'given_name', 'additional_name']
        verbose_name_plural = 'people'

    def __str__(self):
        return " ".join((self.given_name, self.family_name))

    def full_display_name(self):
        name_parts = (self.given_name, self.additional_name
                      or None, self.family_name)
        return " ".join([x for x in name_parts if x])

    full_display_name.short_description = "Full name"

    def get_absolute_url(self):
        return reverse('facts:person-detail',
                       kwargs={
                           'slug': slugify(self.full_display_name()),
                           'identifier': str(self.identifier)
                       })

    def save(self, *args, **kwargs):
        self.description_rendered = render_markdown(self.description)
        super(Person, self).save(*args, **kwargs)
예제 #20
0
class User(AbstractUser):
    """
    CHANGELOG

    Added 20.05.2019
    """
    profile_icon_created = False

    # It is important, that all fields additionally defined for the user model are NOT mandatory, because during the
    # very first "createsuperuser" process of Django a custom mandatory field could not be filled in resulting in an
    # error.

    # First Name and Last Name do not cover name patterns
    # around the globe, thus we use one field, where the full name should be inserted
    name = CharField(_("Name of User"), blank=True, max_length=255)

    # A FilerImageField is a field, which will automatically expose a Filer file upload widget to the admin backend,
    # through which an image file can either be selected from the already existent filer storage folders or freshly
    # be uploaded
    image = FilerImageField(null=True,
                            blank=True,
                            related_name="user_profile_image",
                            on_delete=models.CASCADE)

    # This field will contain the small 50x50px version of the profile image. It will not be directly uploaded by
    # the user, but rather automatically be created from the full size version of the image during the save process
    # of the model.
    profile_icon = ImageField(null=True, blank=True)

    # 21.07.2019
    # This field should contain a short introductory text about the people, like "Hi I am mike from oregon and I just
    # wanna write some stupid shit on this site my interests are.."
    introduction = TextField(_("Introduce yourself"), blank=True)
    # This will contain the age of the member
    age = IntegerField(_("Your Age"), null=True, blank=True)
    country = CharField(_("Your Country"), blank=True, max_length=255)
    profession = CharField(_("Current profession"), blank=True, max_length=255)

    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)

    def get_recent_projects(self, n=5):
        """
        Returns the n latest project posts for this user

        CHANGELOG

        Added 21.07.2019

        :param n:
        :return:
        """
        recent_projects = self.projects.order_by('publishing_date')[0:5]
        return recent_projects

    def get_absolute_url(self):
        """
        Returns the full URL to the web page of this user

        CHANGELOG

        Added 20.05.2019

        :return:
        """
        return reverse("users:detail", kwargs={"username": self.username})

    def save(self, *args, **kwargs):
        """
        This method is being called, when the model is being saved.
        Here we implement the custom behaviour, which creates the 150x50 profile icon from the uploaded full size
        image.

        CHANGELOG

        Added 18.07.2019

        :param args:
        :param kwargs:
        :return:
        """
        # It is important, that the creation of the profile icon is executed after the super() save process, because
        # the base image for the transform within "self.image" might not already be saved as file.
        super(User, self).save(*args, **kwargs)

        if self.image and not self.profile_icon_created:
            # This method will create a 50x50 px version of the Image uploaded to "self.image" and save it to the
            # "self.profile_icon" field. At the end it sets self.profile_icon_created to True to prevent a
            # recursion during the save process.
            self._create_profile_icon()

    def _create_profile_icon(self):
        """
        This method creates the profile icon image, which is a 50x50 px Version of the profile picture, which was
        uploaded by the user and saves it to the "profile_icon" ImageField of the User model.

        CHANGELOG

        Added 18.07.2019

        :return:
        """
        # !! Since the media will be going to the amazon bucket i might have to change this to first download the file
        # via urllib first

        # "self.image" is of the type "Image" from the Filer package.
        # The "file_ptr" attribute is a one to one field mapping to a Filer "File" model, which saves the location
        # of the file. The "path" property of this "File" object now finally contains the full path of the file
        # on the system
        image_bytes = self._get_profile_image_content()
        source_file = tempfile.TemporaryFile()
        source_file.write(image_bytes)

        image_generator = ProfileImage(source=source_file)

        # The "generate" method returns a "_io.BytesIO" object, which describes the byte string for the resulting
        # image file. But we need the actual byte string. It can be extracted from the BytesIO object using its
        # "getvalue" method which returns a normal python byte string
        result_bytesio = image_generator.generate()
        result_bytes = result_bytesio.getvalue()

        # Here we create a temporary file. We simply need it because it works the same way as a normal python file
        # descriptor. A file descriptor is needed to save the "ImageField"
        result_file = tempfile.TemporaryFile()
        result_file.write(result_bytes)

        # "profile_icon_created" is a class attribute, which is initially False. The creation of the profile image
        # will only be triggered if it is False. We need to set it to True, because the "save" method on the
        # profile_icon "ImageField" triggers a save for the whole User for some reason, which would create an
        # infinite recursion.
        self.profile_icon_created = True
        self.profile_icon.save(self.name + '_profile.jpeg', File(result_file))

        source_file.close()

    def _get_profile_image_content(self):
        """
        Returns a byte string, which resembles the content of the image file, which was uploaded as the profile
        picture of the user.

        CHANGELOG

        Added 20.07.2019

        :return:
        """
        if settings.IS_PRODUCTION:
            return self._get_profile_image_content_by_url()
        else:
            return self._get_profile_image_content_by_path()

    def _get_profile_image_content_by_path(self):
        """
        Returns a byte string, which resembled the content of the image file, which was uploaded as the profile
        picture of the user. It does so by getting the actual physical path of the image file from the hard drive
        and reading this files content.

        CHANGELOG

        Added 20.07.2019

        :return:
        """
        # "self.image" is of the type "Image" from the Filer package.
        # The "file_ptr" attribute is a one to one field mapping to a Filer "File" model, which saves the location
        # of the file. The "path" property of this "File" object now finally contains the full path of the file
        # on the system
        path = self.image.file_ptr.path
        with open(path, mode='rb') as image:
            return image.read()

    def _get_profile_image_content_by_url(self):
        """
        Returns a byte string, which resembles the content of the image file, which was uploaded as the profile
        picture of the user. It does so by downloading the content from the url of the image file within the media
        folder of the website.

        CHANGELOG

        Added 20.07.2019

        :return:
        """
        # "self.image" is of the type "Image" from the Filer package.
        # The "file_ptr" attribute is a one to one field mapping to a Filer "File" model, which saves the location
        # of the file. The "path" property of this "File" object now finally contains the full path of the file
        # on the system
        url = self.image.file_ptr.url
        http = urllib3.PoolManager()
        with http.request('GET', url, preload_content=False) as image:
            return image.read()
예제 #21
0
class Article(CustomArticleMixin, TranslatedAutoSlugifyMixin,
              TranslationHelperMixin, TranslatableModel):

    # TranslatedAutoSlugifyMixin options
    slug_source_field_name = 'title'
    slug_default = _('untitled-article')
    # when True, updates the article's search_data field
    # whenever the article is saved or a plugin is saved
    # on the article's content placeholder.
    update_search_on_save = getattr(
        settings,
        'ALDRYN_NEWSBLOG_UPDATE_SEARCH_DATA_ON_SAVE', False) or getattr(
            settings, 'ALDRYN_NEWSBLOG_AUTO_CALCULATE_READ_TIME', False)

    translations = TranslatedFields(
        title=models.CharField(_('title'), max_length=234),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_('Used in the URL. If changed, the URL will change. '
                        'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('Summary'),
            default='',
            help_text=_(
                'The Summary gives the reader the main idea of the story, this '
                'is useful in overviews, lists or as an introduction to your '
                'article.'),
            blank=True,
        ),
        read_time=models.CharField(max_length=255,
                                   verbose_name=_('Read time'),
                                   blank=True,
                                   default=''),
        meta_title=models.CharField(max_length=255,
                                    verbose_name=_('meta title'),
                                    blank=True,
                                    default=''),
        meta_description=models.TextField(verbose_name=_('meta description'),
                                          blank=True,
                                          default=''),
        meta_keywords=models.TextField(verbose_name=_('meta keywords'),
                                       blank=True,
                                       default=''),
        meta={'unique_together': ((
            'language_code',
            'slug',
        ), )},
        search_data=models.TextField(blank=True, editable=False),
        author_trans=models.ForeignKey(Person,
                                       on_delete=models.SET_NULL,
                                       related_name='articles_trans',
                                       null=True,
                                       blank=True,
                                       verbose_name=_('author')),
        author_2_trans=models.ForeignKey(Person,
                                         on_delete=models.SET_NULL,
                                         related_name='articles_trans_2',
                                         null=True,
                                         blank=True,
                                         verbose_name=_('second author')),
        author_3_trans=models.ForeignKey(Person,
                                         on_delete=models.SET_NULL,
                                         related_name='articles_trans_3',
                                         null=True,
                                         blank=True,
                                         verbose_name=_('third author')),
        is_published_trans=models.BooleanField(_('is published'),
                                               default=False,
                                               db_index=True),
        is_featured_trans=models.BooleanField(_('is featured'),
                                              default=False,
                                              db_index=True),
    )

    content = PlaceholderField('newsblog_article_content',
                               related_name='newsblog_article_content')
    related_articles = PlaceholderField(
        'newsblog_related_articles', related_name='newsblog_related_articles')
    article_carousel = PlaceholderField(
        'newsblog_article_carousel', related_name='newsblog_article_carousel')
    article_sidebar = PlaceholderField('newsblog_article_sidebar',
                                       related_name='newsblog_article_sidebar')
    hide_authors = models.BooleanField(
        _('Hide Authors'),
        default=False,
    )
    author = models.ForeignKey(Person,
                               on_delete=models.SET_NULL,
                               null=True,
                               blank=True,
                               verbose_name=_('author'))
    author_2 = models.ForeignKey(Person,
                                 on_delete=models.SET_NULL,
                                 related_name='author_2',
                                 null=True,
                                 blank=True,
                                 verbose_name=_('second author'))
    author_3 = models.ForeignKey(Person,
                                 on_delete=models.SET_NULL,
                                 related_name='author_3',
                                 null=True,
                                 blank=True,
                                 verbose_name=_('third author'))
    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              on_delete=models.SET_NULL,
                              verbose_name=_('owner'),
                              null=True,
                              blank=True)
    app_config = AppHookConfigField(
        NewsBlogConfig,
        on_delete=models.CASCADE,
        verbose_name=_('Section'),
        help_text='',
    )
    locations = SortedManyToManyField('js_locations.location',
                                      verbose_name=_('locations'),
                                      blank=True)
    categories = CategoryManyToManyField('aldryn_categories.Category',
                                         verbose_name=_('categories'),
                                         blank=True)
    services = SortedManyToManyField('js_services.Service',
                                     verbose_name=_('services'),
                                     blank=True)
    feeds = models.ManyToManyField(NewsBlogFeed,
                                   verbose_name=_('feeds'),
                                   blank=True)
    publishing_date = models.DateTimeField(_('publishing date'), default=now)
    is_published = models.BooleanField(_('is published'),
                                       default=False,
                                       db_index=True)
    is_featured = models.BooleanField(_('is featured'),
                                      default=False,
                                      db_index=True)
    featured_image = FilerImageField(
        verbose_name=_('featured image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    share_image = FilerImageField(
        verbose_name=_('social share image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text=
        'This image will only be shown on social channels. Minimum size: 1200x630px',
        related_name='+')
    logo_image = FilerImageField(verbose_name=_('logo image'),
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL,
                                 related_name='+')
    svg_image = FilerFileField(
        verbose_name=_('logo SVG'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    medium = models.ForeignKey(ArticleMedium,
                               on_delete=models.SET_NULL,
                               verbose_name=_('medium'),
                               null=True,
                               blank=True)

    show_on_sitemap = models.BooleanField(_('Show on sitemap'),
                                          null=False,
                                          default=True)
    show_on_xml_sitemap = models.BooleanField(_('Show on xml sitemap'),
                                              null=False,
                                              default=True)
    noindex = models.BooleanField(_('noindex'), null=False, default=False)
    nofollow = models.BooleanField(_('nofollow'), null=False, default=False)
    canonical_url = models.CharField(blank=True,
                                     null=True,
                                     max_length=255,
                                     verbose_name=_('Canonical URL'))

    layout = models.CharField(blank=True,
                              default='',
                              max_length=60,
                              verbose_name=_('layout'))
    custom_fields = JSONField(blank=True, null=True)
    # Setting "symmetrical" to False since it's a bit unexpected that if you
    # set "B relates to A" you immediately have also "A relates to B". It have
    # to be forced to False because by default it's True if rel.to is "self":
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L2144
    #
    # which in the end causes to add reversed releted-to entry as well:
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L977
    related = SortedManyToManyField('self',
                                    verbose_name=_('specific articles'),
                                    blank=True,
                                    symmetrical=False)

    objects = RelatedManager()
    all_objects = AllManager()
    search_objects = SearchManager()

    class Meta:
        ordering = ['-publishing_date']

    def get_class(self):
        '''Return class name'''
        return self.__class__.__name__

    @property
    def type(self):
        '''Article Type / Section.'''
        return self.app_config

    @cached_property
    def cached_type(self):
        '''Article Type / Section.'''
        return self.app_config

    @property
    def type_slug(self):
        '''Article Type / Section Machine Name'''
        return self.app_config.namespace

    @property
    def published(self):
        """
        Returns True only if the article (is_published == True) AND has a
        published_date that has passed.
        """
        language = get_current_language()
        return self.published_for_language(language)

    def published_for_language(self, language):
        if TRANSLATE_IS_PUBLISHED:
            return ((self.safe_translation_getter('is_published_trans',
                                                  language_code=language,
                                                  any_language=False) or False)
                    and self.publishing_date <= now())
        return (self.is_published and self.publishing_date <= now())

    @property
    def authors(self):
        authors = []
        if TRANSLATE_AUTHORS:
            if self.author_trans and self.author_trans.published:
                authors.append(self.author_trans)
            if self.author_2_trans and self.author_2_trans.published:
                authors.append(self.author_2_trans)
            if self.author_3_trans and self.author_3_trans.published:
                authors.append(self.author_3_trans)
        else:
            if self.author and self.author.published:
                authors.append(self.author)
            if self.author_2 and self.author_2.published:
                authors.append(self.author_2)
            if self.author_3 and self.author_3.published:
                authors.append(self.author_3)
        return authors

    @property
    def future(self):
        """
        Returns True if the article is published but is scheduled for a
        future date/time.
        """
        return (self.is_published and self.publishing_date > now())

    def get_absolute_url(self, language=None):
        """Returns the url for this Article in the selected permalink format."""
        if not language:
            language = get_current_language()
        kwargs = {}
        permalink_type = self.cached_type.permalink_type
        if 'y' in permalink_type:
            kwargs.update(year=self.publishing_date.year)
        if 'm' in permalink_type:
            kwargs.update(month="%02d" % self.publishing_date.month)
        if 'd' in permalink_type:
            kwargs.update(day="%02d" % self.publishing_date.day)
        if 'i' in permalink_type:
            kwargs.update(pk=self.pk)
        if 's' in permalink_type:
            slug, lang = self.known_translation_getter('slug',
                                                       default=None,
                                                       language_code=language)
            if slug and lang:
                site_id = getattr(settings, 'SITE_ID', None)
                if get_redirect_on_fallback(language, site_id):
                    language = lang
                kwargs.update(slug=slug)

        if self.cached_type.namespace:
            namespace = self.cached_type.namespace
            try:
                reverse('{0}:article-list'.format(namespace))
            except:
                namespace = NewsBlogConfig.default_namespace
        else:
            namespace = NewsBlogConfig.default_namespace

        with override(language):
            return reverse('{0}:article-detail'.format(namespace),
                           kwargs=kwargs)

    def get_public_url(self, language=None):
        if not language:
            language = get_current_language()
        if not TRANSLATE_IS_PUBLISHED and self.published:
            return self.get_absolute_url(language)
        if (TRANSLATE_IS_PUBLISHED and \
                (self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False) and \
                self.publishing_date <= now()):
            return self.get_absolute_url(language)
        return ''

    def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Article.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_current_language()
        if request is None:
            request = get_request(language=language)
        title = self.safe_translation_getter('title', '')
        description = self.safe_translation_getter('lead_in', '')
        text_bits = [title, strip_tags(description)]
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for service in self.services.all():
            text_bits.append(
                force_unicode(service.safe_translation_getter('title')))
        text_bits.append('=c=o=n=t=e=n=t=')
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        # Update the search index
        if self.update_search_on_save:
            self.search_data = self.get_search_data()
            auto_read_time = getattr(
                settings, 'ALDRYN_NEWSBLOG_AUTO_CALCULATE_READ_TIME', False)
            if callable(auto_read_time):
                auto_read_time = auto_read_time(self)
            if auto_read_time and self.app_config.auto_read_time:
                read_time = self.get_read_time()
                if read_time:
                    self.read_time = read_time
        # Ensure there is an owner.
        if self.app_config.create_authors and self.owner and self.author is None:
            if hasattr(Person, 'first_name') and hasattr(Person, 'last_name'):
                defaults = {
                    'first_name': self.owner.first_name,
                    'last_name': self.owner.last_name,
                }
            else:
                defaults = {
                    'name':
                    ' '.join((
                        self.owner.first_name,
                        self.owner.last_name,
                    )),
                }
            self.author = Person.objects.get_or_create(user=self.owner,
                                                       defaults=defaults)[0]
        # slug would be generated by TranslatedAutoSlugifyMixin
        #if not self.medium:
        #self.medium = ArticleMedium.objects.first()
        super(Article, self).save(*args, **kwargs)

    def get_read_time(self):
        if '=c=o=n=t=e=n=t=' in self.search_data:
            read_time_function = getattr(
                settings, 'ALDRYN_NEWSBLOG_READ_TIME_FUNCTION',
                lambda x: x // 200 + (0 if x % 200 == 0 else 1))
            return read_time_function(
                len(self.search_data.split('=c=o=n=t=e=n=t=')[1].split()))

    def get_placeholders(self):
        return [
            self.content, self.related_articles, self.article_carousel,
            self.article_sidebar
        ]

    def __str__(self):
        return self.safe_translation_getter('title', any_language=True)

    def get_related_articles_by_services(self, article_category=None):
        articles = self.__class__.objects.published().filter(
            services__in=self.services.all()).distinct().exclude(id=self.id)
        if article_category:
            return articles.namespace(article_category)
        return articles

    def get_related_articles_by_categories(self, article_category=None):
        articles = self.__class__.objects.published().filter(
            categories__in=self.categories.all()).distinct().exclude(
                id=self.id)
        if article_category:
            return articles.namespace(article_category)
        return articles

    def related_articles_by_services(self):
        return self.get_related_articles_by_services()

    def related_articles_by_categories(self):
        return self.get_related_articles_by_categories()

    def related_articles_same_type_by_services(self):
        return self.get_related_articles_by_services(self.app_config.namespace)

    def related_articles_same_type_by_categories(self):
        return self.get_related_articles_by_categories(
            self.app_config.namespace)
예제 #22
0
class SlidePlugin(CMSPlugin):
    LINK_TARGETS = (
        ('', _('same window')),
        ('_blank', _('new window')),
        ('_parent', _('parent window')),
        ('_top', _('topmost frame')),
    )

    cmsplugin_ptr = CMSPluginField()
    image = FilerImageField(verbose_name=_('image'), blank=True, null=True)
    content = HTMLField("Content", blank=True, null=True)
    url = models.CharField(_("Link"), max_length=255, blank=True, null=True)
    page_link = PageField(
        verbose_name=_('Page'),
        blank=True,
        null=True,
        help_text=_("A link to a page has priority over a text link.")
    )
    target = models.CharField(
        verbose_name=_("target"),
        max_length=100,
        blank=True,
        choices=LINK_TARGETS,
    )
    link_anchor = models.CharField(
        verbose_name=_("link anchor"),
        max_length=128,
        blank=True,
    )
    link_text = models.CharField(
        verbose_name=_('link text'),
        max_length=200,
        blank=True
    )

    def __unicode__(self):
        image_text = content_text = ''

        if self.image_id:
            image_text = u'%s' % (self.image.name or self.image.original_filename)
        if self.content:
            text = strip_tags(self.content).strip()
            if len(text) > 100:
                content_text = u'%s...' % text[:100]
            else:
                content_text = u'%s' % text

        if image_text and content_text:
            return u'%s (%s)' % (image_text, content_text)
        else:
            return image_text or content_text

    def copy_relations(self, oldinstance):
        self.image_id = oldinstance.image_id
        self.page_link_id = oldinstance.page_link_id

    def get_link(self):
        link = self.url or u''

        if self.page_link_id:
            link = self.page_link.get_absolute_url()

        if self.link_anchor:
            link += u'#' + self.link_anchor
        return link
예제 #23
0
class GalleryImage(models.Model):
    image_file = FilerImageField(related_name='gallery_images')
    gallery = models.ForeignKey(Gallery)
예제 #24
0
class PageFieldExtension(PageExtension):
    subtitle = models.CharField(max_length=255, blank=True)
    background_image = FilerImageField(blank=True)
예제 #25
0
class ServiceProvider(PolymorphicTranslatableShuupModel):
    """
    Entity that provides services.

    Good examples of service providers are `Carrier` and
    `PaymentProcessor`.

    When subclassing `ServiceProvider`, set value for `service_model`
    class attribute.  It should be a model class which is subclass of
    `Service`.
    """
    identifier = InternalIdentifierField(unique=True)
    enabled = models.BooleanField(default=True, verbose_name=_("enabled"))
    name = TranslatedField(any_language=True)
    logo = FilerImageField(blank=True,
                           null=True,
                           on_delete=models.SET_NULL,
                           verbose_name=_("logo"))

    base_translations = TranslatedFields(name=models.CharField(
        max_length=100, verbose_name=_("name")), )

    #: Model class of the provided services (subclass of `Service`)
    service_model = None

    def get_service_choices(self):
        """
        Get all service choices of this provider.

        Subclasses should implement this method.

        :rtype: list[ServiceChoice]
        """
        raise NotImplementedError

    def create_service(self, choice_identifier, **kwargs):
        """
        Create a service for given choice identifier.

        Subclass implementation may attach some `behavior components
        <ServiceBehaviorComponent>` to the created service.

        Subclasses should provide implementation for `_create_service`
        or override this.  Base class implementation calls the
        `_create_service` method with resolved `choice_identifier`.

        :type choice_identifier: str|None
        :param choice_identifier:
          Identifier of the service choice to use.  If None, use the
          default service choice.
        :rtype: shuup.core.models.Service
        """
        if choice_identifier is None:
            choice_identifier = self.get_service_choices()[0].identifier
        return self._create_service(choice_identifier, **kwargs)

    def _create_service(self, choice_identifier, **kwargs):
        """
        Create a service for given choice identifier.

        :type choice_identifier: str
        :rtype: shuup.core.models.Service
        """
        raise NotImplementedError

    def get_effective_name(self, service, source):
        """
        Get effective name of the service for given order source.

        Base class implementation will just return name of the given
        service, but that may be changed in a subclass.

        :type service: shuup.core.models.Service
        :type source: shuup.core.order_creator.OrderSource
        :rtype: str
        """
        return service.name
예제 #26
0
class Category(TranslatedAutoSlugifyMixin, TranslationHelperMixin,
               TranslatableModel, NS_Node):
    """
    A category is hierarchical. The structure is implemented with django-
    treebeard's Nested Sets trees, which has the performance characteristics
    we're after, namely: fast reads at the expense of write-speed.
    """
    slug_source_field_name = 'name'

    translations = TranslatedFields(
        name=models.CharField(
            _('name'),
            blank=False,
            default='',
            max_length=255,
        ),
        slug=models.SlugField(
            _('slug'),
            blank=True,
            default='',
            help_text=_('Provide a “slug” or leave blank for an automatically '
                        'generated one.'),
            max_length=255,
        ),
        summary=models.TextField(
            _('summary'),
            blank=True,
            default='',
        ),
        image=FilerImageField(
            null=True,
            blank=True,
        ),
        landing_page=models.CharField(
            _('landing page'),
            max_length=255,
            blank=True,
            default='',
        ),
        link=models.URLField(
            _('link'),
            blank=True,
            default='',
        ),
        no_url=models.BooleanField(
            _('no url'),
            blank=True,
            default=False,
        ),
        meta={'unique_together': ((
            'language_code',
            'slug',
        ), )})

    class Meta:
        verbose_name = _('category')
        verbose_name_plural = _('categories')

    objects = CategoryManager()

    def delete(self, **kwargs):
        #
        # We're managing how the two superclasses (TranslateableModel and
        # NS_Node) perform deletion together here.
        #
        # INFO: There currently is a bug in parler where it will pass along
        #       'using' as a positional argument, which does not work in
        #       Djangos implementation. So we skip it.
        self.__class__.objects.filter(pk=self.pk).delete(**kwargs)
        from parler.cache import _delete_cached_translations
        _delete_cached_translations(self)
        models.Model.delete(self, **kwargs)

    def __str__(self):
        name = self.safe_translation_getter('name', any_language=True)
        return escape(name)
예제 #27
0
class Shop(ChangeProtected, TranslatableShoopModel):
    protected_fields = ["currency", "prices_include_tax"]
    change_protect_message = _(
        "The following fields cannot be changed since there are existing orders for this shop"
    )

    identifier = InternalIdentifierField(unique=True)
    domain = models.CharField(max_length=128,
                              blank=True,
                              null=True,
                              unique=True,
                              verbose_name=_("domain"))
    status = EnumIntegerField(ShopStatus,
                              default=ShopStatus.DISABLED,
                              verbose_name=_("status"))
    owner = models.ForeignKey("Contact",
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL,
                              verbose_name=_("contact"))
    options = JSONField(blank=True, null=True, verbose_name=_("options"))
    currency = CurrencyField(default=_get_default_currency,
                             verbose_name=_("currency"))
    prices_include_tax = models.BooleanField(
        default=True, verbose_name=_("prices include tax"))
    logo = FilerImageField(verbose_name=_("logo"),
                           blank=True,
                           null=True,
                           on_delete=models.SET_NULL)
    maintenance_mode = models.BooleanField(verbose_name=_("maintenance mode"),
                                           default=False)
    contact_address = models.ForeignKey("MutableAddress",
                                        verbose_name=_("contact address"),
                                        blank=True,
                                        null=True,
                                        on_delete=models.SET_NULL)

    translations = TranslatedFields(
        name=models.CharField(max_length=64, verbose_name=_("name")),
        public_name=models.CharField(max_length=64,
                                     verbose_name=_("public name")),
        maintenance_message=models.CharField(
            max_length=300, blank=True, verbose_name=_("maintenance message")))

    def __str__(self):
        return self.safe_translation_getter("name",
                                            default="Shop %d" % self.pk)

    def create_price(self, value):
        """
        Create a price with given value and settings of this shop.

        Takes the ``prices_include_tax`` and ``currency`` settings of
        this Shop into account.

        :type value: decimal.Decimal|int|str
        :rtype: shoop.core.pricing.Price
        """
        if self.prices_include_tax:
            return TaxfulPrice(value, self.currency)
        else:
            return TaxlessPrice(value, self.currency)

    def _are_changes_protected(self):
        return Order.objects.filter(shop=self).exists()
예제 #28
0
class Category(MPTTModel, TranslatableModel):
    parent = TreeForeignKey(
        'self', null=True, blank=True, related_name='children',
        verbose_name=_('parent category'), on_delete=models.CASCADE)
    shops = models.ManyToManyField("Shop", blank=True, related_name="categories", verbose_name=_("shops"))
    identifier = InternalIdentifierField(unique=True)
    status = EnumIntegerField(CategoryStatus, db_index=True, verbose_name=_('status'), default=CategoryStatus.INVISIBLE)
    image = FilerImageField(verbose_name=_('image'), blank=True, null=True, on_delete=models.SET_NULL)
    ordering = models.IntegerField(default=0, verbose_name=_('ordering'))
    visibility = EnumIntegerField(
        CategoryVisibility, db_index=True, default=CategoryVisibility.VISIBLE_TO_ALL,
        verbose_name=_('visibility limitations')
    )
    visibility_groups = models.ManyToManyField(
        "ContactGroup", blank=True, verbose_name=_('visible for groups'), related_name=u"visible_categories"
    )

    translations = TranslatedFields(
        name=models.CharField(max_length=128, verbose_name=_('name')),
        description=models.TextField(verbose_name=_('description'), blank=True),
        slug=models.SlugField(blank=True, null=True, verbose_name=_('slug'))
    )

    objects = CategoryManager()

    class Meta:
        ordering = ('tree_id', 'lft')
        verbose_name = _('category')
        verbose_name_plural = _('categories')

    class MPTTMeta:
        order_insertion_by = ["ordering"]

    def __str__(self):
        return self.safe_translation_getter("name", any_language=True)

    def is_visible(self, customer):
        if customer and customer.is_all_seeing:
            return (self.status != CategoryStatus.DELETED)
        if self.status != CategoryStatus.VISIBLE:
            return False
        if not customer or customer.is_anonymous:
            if self.visibility != CategoryVisibility.VISIBLE_TO_ALL:
                return False
        else:
            if self.visibility == CategoryVisibility.VISIBLE_TO_GROUPS:
                group_ids = customer.groups.all().values_list("id", flat=True)
                return self.visibility_groups.filter(id__in=group_ids).exists()
        return True

    @staticmethod
    def _get_slug_name(self, translation):
        if self.status == CategoryStatus.DELETED:
            return None
        return getattr(translation, "name", self.pk)

    def delete(self, using=None):
        raise NotImplementedError("Not implemented: Use `soft_delete()` for categories.")

    @atomic
    def soft_delete(self, user=None):
        if not self.status == CategoryStatus.DELETED:
            for shop_product in self.primary_shop_products.all():
                shop_product.primary_category = None
                shop_product.save()
            for shop_product in self.shop_products.all():
                shop_product.categories.remove(self)
                shop_product.save()
            for product in self.primary_products.all():
                product.category = None
                product.save()
            for child in self.children.all():
                child.parent = None
                child.save()
            self.status = CategoryStatus.DELETED
            self.add_log_entry("Deleted.", kind=LogEntryKind.DELETION, user=user)
            self.save()
            category_deleted.send(sender=type(self), category=self)

    def save(self, *args, **kwargs):
        rv = super(Category, self).save(*args, **kwargs)
        generate_multilanguage_slugs(self, self._get_slug_name)
        return rv
예제 #29
0
class Bootstrap4GridRow(CMSPlugin):
    """
    Layout > Grid: "Row" Plugin
    https://getbootstrap.com/docs/4.0/layout/grid/
    """
    layout = models.CharField(
        verbose_name=_('Layout'),
        #choices=GRID_ROW_LAYOUT_CHOICES,
        blank=True,
        max_length=255,
        help_text=_('Select a layout'),
    )
    vertical_alignment = models.CharField(
        verbose_name=_('Vertical alignment'),
        choices=GRID_ROW_VERTICAL_ALIGNMENT_CHOICES,
        blank=True,
        max_length=255,
        help_text=mark_safe_lazy(
            _('Read more in the <a href="{link}" target="_blank">documentation</a>.'
              ).
            format(
                link=
                'https://getbootstrap.com/docs/4.0/layout/grid/#vertical-alignment'
            )),
    )
    horizontal_alignment = models.CharField(
        verbose_name=_('Horizontal alignment'),
        choices=GRID_ROW_HORIZONTAL_ALIGNMENT_CHOICES,
        blank=True,
        max_length=255,
        help_text=mark_safe_lazy(
            _('Read more in the <a href="{link}" target="_blank">documentation</a>.'
              ).
            format(
                link=
                'https://getbootstrap.com/docs/4.0/layout/grid/#horizontal-alignment'
            )),
    )
    full_width = models.BooleanField(
        verbose_name=_('Show Full Width'),
        default=False,
    )
    gutters = models.BooleanField(
        verbose_name=_('Remove gutters'),
        default=False,
        help_text=_('Removes the marginal gutters from the grid.'),
    )
    tag_type = TagTypeField()
    attributes = AttributesField()
    background_color = RGBColorField(verbose_name=_('Background Color'),
                                     blank=True,
                                     null=True)
    background_image = FilerImageField(verbose_name=_('Background Image'),
                                       on_delete=models.SET_NULL,
                                       null=True,
                                       blank=True,
                                       related_name='row_bg_image')
    background_svg = FilerFileField(verbose_name=_('Background SVG'),
                                    on_delete=models.SET_NULL,
                                    null=True,
                                    blank=True,
                                    related_name='+')
    background_video = models.CharField(
        verbose_name=_('Background Video'),
        blank=True,
        max_length=255,
    )
    parallax = models.BooleanField(
        verbose_name=_('Parallax'),
        default=False,
    )
    icon = Icon(verbose_name=_('Icon'), null=True, blank=True)
    title = models.CharField(
        verbose_name=_('Title'),
        null=True,
        blank=True,
        max_length=255,
    )
    display_title = models.BooleanField(
        verbose_name=_('Display Title'),
        default=False,
    )

    def __str__(self):
        return self.title or str(self.pk)

    def get_short_description(self):
        instance = self.get_plugin_instance()[0]

        if not instance:
            return ugettext('<empty>')

        column_count = len(self.child_plugin_instances or [])
        #column_count_str = ungettext(
        #'(1 col)',
        #'(%(count)i col)',
        #column_count
        #) % {'count': column_count}
        # column_count_str += ' .{}'.format(
        #     ' .'.join(instance.attributes['class'].split())
        # )

        return '(%s) %s' % (column_count, self.title or '')
예제 #30
0
class Migration(migrations.Migration):

    dependencies = [
        ('cms', '__latest__'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('filer', '__latest__'),
    ]

    operations = [
        migrations.CreateModel(
            name='Post',
            fields=[
                ('id',
                 models.AutoField(verbose_name='ID',
                                  serialize=False,
                                  auto_created=True,
                                  primary_key=True)),
                ('title',
                 models.CharField(help_text='The post title.',
                                  max_length=255,
                                  verbose_name='Title')),
                ('slug',
                 models.SlugField(
                     help_text='The name (slug) for the post, used in URLs.',
                     unique=True,
                     max_length=255,
                     verbose_name='slug')),
                ('excerpt', models.TextField(verbose_name='Excerpt',
                                             blank=True)),
                ('creation_date',
                 models.DateTimeField(help_text="The post's creation time.",
                                      auto_now_add=True)),
                ('publication_date',
                 models.DateTimeField(
                     default=django.utils.timezone.now,
                     help_text=
                     'Used in the URL. If changed, the URL will change.',
                     verbose_name='Publication date',
                     db_index=True)),
                ('author',
                 models.ForeignKey(related_name='posts',
                                   verbose_name='Author',
                                   to=settings.AUTH_USER_MODEL,
                                   help_text='The author of the post.')),
                ('content',
                 PlaceholderField(related_name='post_content',
                                  slotname='post content',
                                  editable=False,
                                  to='cms.Placeholder',
                                  help_text='The post content.',
                                  null=True)),
                ('featured_image',
                 FilerImageField(related_name='blog_post_featured_images',
                                 verbose_name='Featured Image',
                                 blank=True,
                                 null=True,
                                 to='filer.Image',
                                 help_text='Featured image for this post')),
            ],
            options={
                'ordering': ('-publication_date', ),
                'get_latest_by': 'publication_date',
            },
        ),
    ]