class Page(MPTTModel):
    title = models.CharField(max_length=200)
    parent = models.ForeignKey('self',
                               related_name='children',
                               blank=True,
                               null=True,
                               on_delete=models.CASCADE)

    template = Template(
        key='test',
        regions=[
            Region(key='main', title='main region'),
            Region(key='sidebar', title='sidebar region', inherited=True),
        ],
    )

    class Meta:
        verbose_name = 'page'
        verbose_name_plural = 'pages'

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

    @property
    def regions(self):
        return self.template.regions
class Page(models.Model):
    title = models.CharField(max_length=200)
    parent = models.ForeignKey("self",
                               related_name="children",
                               blank=True,
                               null=True,
                               on_delete=models.CASCADE)

    template = Template(
        key="test",
        regions=[
            Region(key="main", title="main region"),
            Region(key="sidebar", title="sidebar region", inherited=True),
        ],
    )

    class Meta:
        verbose_name = "page"
        verbose_name_plural = "pages"

    def get_absolute_url(self):
        return reverse("page_detail", kwargs={"pk": self.pk})

    @property
    def regions(self):
        return self.template.regions
Exemple #3
0
class Page(
        AbstractPage,
        AppsMixin,  # For adding the articles app to pages through the CMS.
        TemplateMixin,  # Two page templates, one with only a main
        # region and another with a sidebar as well.
        MenuMixin,  # We have a main and a footer navigation (meta).
        LanguageMixin,  # We're building a multilingual CMS. (Also,
        # feincms3.apps depends on LanguageMixin
        # currently.)
        RedirectMixin,  # Allow redirecting pages to other pages and/or arbitrary
        # URLs.
):

    # TemplateMixin
    TEMPLATES = [
        Template(
            key="standard",
            title=_("standard"),
            template_name="pages/standard.html",
            regions=(Region(key="main", title=_("Main")), ),
        ),
        Template(
            key="with-sidebar",
            title=_("with sidebar"),
            template_name="pages/with-sidebar.html",
            regions=(
                Region(key="main", title=_("Main")),
                Region(key="sidebar", title=_("Sidebar")),
            ),
        ),
    ]

    # MenuMixin
    MENUS = [("main", _("main")), ("footer", _("footer"))]

    # AppsMixin. We have two apps, one is for company PR, the other
    # for a more informal blog.
    #
    # NOTE! The app names (first element in the tuple) have to match the
    # article categories exactly for URL reversing and filtering articles by
    # app to work! (See app.articles.models.Article.CATEGORIES)
    APPLICATIONS = [
        ("publications", _("publications"), {
            "urlconf": "testapp.articles_urls"
        }),
        ("blog", _("blog"), {
            "urlconf": "testapp.articles_urls"
        }),
        (
            "stuff-with-required",
            "stuff-with-required",
            {
                "urlconf": "stuff-with-required",
                "required_fields": ("optional", "not_editable"),
            },
        ),
    ]

    optional = models.IntegerField(blank=True, null=True)
    not_editable = models.IntegerField(blank=True, null=True, editable=False)
Exemple #4
0
class Article(models.Model):
    created = models.DateTimeField(_("_created"), auto_now_add=True)
    updated = models.DateTimeField(_("_updated"), auto_now=True)
    headline = models.CharField(_("_headline"), max_length=200)
    teaser = models.TextField(_("_teaser"))
    slug = models.SlugField(_("_slug"), max_length=200)
    in_menu = models.BooleanField(_("_in_menu"), default=False)
    menu_order = models.IntegerField(_("_menu_order"), null=True, blank=True)

    tags = TaggableManager(blank=True)

    regions = [
        Region(key="main", title="main region"),
        Region(key="sidebar", title="sidebar region", inherited=False),
    ]

    class Meta:
        unique_together = (("slug", "created"), )
        ordering = ["-updated"]

    def get_absolute_url(self):
        if self.slug == "about":
            return "/"
        elif self.in_menu:
            return f"/{self.slug}"
        else:
            return f"/articles/{self.created.year}/{self.created.month}/{self.slug}"

    def get_unique_identifier(self):
        return f"{self.created.year}_{self.created.month}_{self.slug}"

    def __str__(self):
        return self.headline
Exemple #5
0
class Page(
    AbstractPage,
    AppsMixin,      # For adding the articles app to pages through the CMS.
    TemplateMixin,  # Two page templates, one with only a main
                    # region and another with a sidebar as well.
    MenuMixin,      # We have a main and a footer navigation (meta).
    LanguageMixin,  # We're building a multilingual CMS. (Also,
                    # feincms3.apps depends on LanguageMixin
                    # currently.)
    RedirectMixin,  # Allow redirecting pages to other pages and/or arbitrary
                    # URLs.
):

    # TemplateMixin
    TEMPLATES = [
        Template(
            key='standard',
            title=_('standard'),
            template_name='pages/standard.html',
            regions=(
                Region(key='main', title=_('Main')),
            ),
        ),
        Template(
            key='with-sidebar',
            title=_('with sidebar'),
            template_name='pages/with-sidebar.html',
            regions=(
                Region(key='main', title=_('Main')),
                Region(key='sidebar', title=_('Sidebar')),
            ),
        ),
    ]

    # MenuMixin
    MENUS = [
        ('main', _('main')),
        ('footer', _('footer')),
    ]

    # AppsMixin. We have two apps, one is for company PR, the other
    # for a more informal blog.
    #
    # NOTE! The app names (first element in the tuple) have to match the
    # article categories exactly for URL reversing and filtering articles by
    # app to work! (See app.articles.models.Article.CATEGORIES)
    APPLICATIONS = [
        ('publications', _('publications'), {
            'urlconf': 'testapp.articles_urls',
        }),
        ('blog', _('blog'), {
            'urlconf': 'testapp.articles_urls',
        }),
    ]

    objects = PageManager()
class Article(models.Model):
    title = models.CharField(max_length=200)

    regions = [
        Region(key="main", title="main region"),
        Region(key="sidebar", title="sidebar region"),
    ]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("article_detail", kwargs={"pk": self.pk})
class Article(models.Model):
    title = models.CharField(max_length=200)

    regions = [
        Region(key='main', title='main region'),
        Region(key='sidebar', title='sidebar region'),
    ]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('article_detail', kwargs={'pk': self.pk})
Exemple #8
0
class Page(
        AbstractPage,
        PageTypeMixin,  # For adding the articles app to pages through the CMS.
        MenuMixin,  # We have a main and a footer navigation (meta).
        LanguageMixin,  # We're building a multilingual CMS. (Also,
        # feincms3.applications depends on LanguageMixin
        # currently.)
):
    TYPES = [
        TemplateType(
            key="standard",
            title=_("standard"),
            template_name="pages/standard.html",
            regions=(Region(key="main", title=_("Main")), ),
        ),
        TemplateType(
            key="with-sidebar",
            title=_("with sidebar"),
            template_name="pages/with-sidebar.html",
            regions=(
                Region(key="main", title=_("Main")),
                Region(key="sidebar", title=_("Sidebar")),
            ),
        ),
        ApplicationType(
            key="publications",
            title=_("publications"),
            urlconf="app.articles.urls",
            regions=(Region(key="main", title=_("Main")), ),
        ),
        ApplicationType(
            key="blog",
            title=_("blog"),
            urlconf="app.articles.urls",
            regions=(Region(key="main", title=_("Main")), ),
        ),
    ]

    # MenuMixin
    MENUS = [
        ("main", _("main")),
        ("footer", _("footer")),
    ]
Exemple #9
0
def get_template_list(app_name, templates):
    return [
        Template(
            key=template[0],
            title=_(template[0]).title(),
            template_name=f"{app_name}/{template[0]}.html",
            regions=[
                Region(key=region, title=region.title(), inherited=True)
                for region in template[1]
            ]
        ) for template in templates
    ]
Exemple #10
0
class Page(AbstractPage, PageTypeMixin, MenuMixin, LanguageMixin, RedirectMixin):
    # MenuMixin
    MENUS = [("main", _("main")), ("footer", _("footer"))]

    # PageTypeMixin. We have two templates and four apps.
    TYPES = [
        TemplateType(
            key="standard",
            title=_("standard"),
            template_name="pages/standard.html",
            regions=(Region(key="main", title=_("Main")),),
        ),
        TemplateType(
            key="with-sidebar",
            title=_("with sidebar"),
            template_name="pages/with-sidebar.html",
            regions=(
                Region(key="main", title=_("Main")),
                Region(key="sidebar", title=_("Sidebar")),
            ),
        ),
        ApplicationType(
            key="publications",
            title=_("publications"),
            urlconf="testapp.articles_urls",
        ),
        ApplicationType(
            key="blog",
            title=_("blog"),
            urlconf="testapp.articles_urls",
        ),
        ApplicationType(
            key="stuff-with-required",
            title="stuff-with-required",
            urlconf="importable_module",
            required_fields=("optional", "not_editable"),
        ),
    ]
Exemple #11
0
class Article(models.Model):
    regions = [Region(key="main", title=_("main"))]
Exemple #12
0
class Page(
        AbstractPage,
        AppsMixin,  # For adding the articles app to pages through the CMS.
        TemplateMixin,  # Two page templates, one with only a main
        # region and another with a sidebar as well.
        MenuMixin,  # We have a main and a footer navigation (meta).
        LanguageMixin,  # We're building a multilingual CMS. (Also,
        # feincms3.apps depends on LanguageMixin
        # currently.)
):

    # TemplateMixin
    TEMPLATES = [
        Template(
            key='standard',
            title=_('standard'),
            template_name='pages/standard.html',
            regions=(
                Region(key='main', title=_('Main'), content='contents.main'),
                Region(key='footer',
                       title=_('Footer'),
                       inherited=True,
                       content='contents.footer'),
            ),
        ),
        Template(
            key='with-sidebar',
            title=_('with sidebar'),
            template_name='pages/with-sidebar.html',
            regions=(
                Region(key='main', title=_('Main'), content='contents.main'),
                Region(key='sidebar',
                       title=_('Sidebar'),
                       content='contents.sidebar'),
                Region(key='footer',
                       title=_('Footer'),
                       inherited=True,
                       content='contents.footer'),
            ),
        ),
    ]

    # MenuMixin
    MENUS = [
        ('main', _('main')),
        ('footer', _('footer')),
    ]

    def get_plugins(self):
        renderers = get_available_renderers()
        page = get_object_or_404(Page, pk=self.id)
        contents = contents_for_item(page, PagePlugins)
        data = []
        for region in self.regions:
            new_data = {
                region.key: [
                    dict(renderers[plugin.__class__](plugin),
                         type=plugin.__class__.__name__,
                         lang=page.language_code,
                         parent=page.slug)
                ]
                for plugin in eval(region.content)
            }
            data.append(new_data)
        return data

    # AppsMixin. We have two apps, one is for company PR, the other
    # for a more informal blog.
    #
    # NOTE! The app names (first element in the tuple) have to match the
    # article categories exactly for URL reversing and filtering articles by
    # app to work! (See app.articles.models.Article.CATEGORIES)
    APPLICATIONS = [
        ('publications', _('publications'), {
            'urlconf': 'app.articles.urls',
        }),
        ('blog', _('blog'), {
            'urlconf': 'app.articles.urls',
        }),
    ]
Exemple #13
0
class Location(MetaMixin, TranslationMixin):
    regions = [Region(key="images", title=_("images"))]
    name = models.CharField(max_length=200, verbose_name=_("name"))
    slug = models.SlugField(unique=True)

    street = models.CharField(max_length=200, verbose_name=_("street"))
    city = models.CharField(max_length=100, verbose_name=_("city"))
    zip_code = models.CharField(max_length=20, verbose_name=_("zip code"))
    country = models.CharField(max_length=200, verbose_name=_("country"))
    is_physical = models.BooleanField(
        verbose_name=_("is physical"),
        default=True,
    )

    header_image = ImageField(
        _("header image"),
        formats={
            "full": ["default", "darken", ("crop", (1920, 900))],
            "mobile": ["default", ("crop", (740, 600))],
        },
        auto_add_fields=True,
        blank=True,
        null=True,
    )

    section = models.ForeignKey(Section,
                                models.SET_NULL,
                                blank=True,
                                null=True,
                                verbose_name=_("section"))

    website = models.URLField(blank=True, verbose_name=_("website"))

    lng = models.FloatField(verbose_name=_("longitude"), default=0)
    lat = models.FloatField(verbose_name=_("latitude"), default=0)

    tags = TaggableManager(blank=True)

    def maps(self):
        return settings.MAPS_URL.format(location=self)

    def __str__(self):
        return self.name

    @property
    def title(self):
        return self.name

    @property
    def address(self):
        return f"{self.street}, {self.zip_code} {self.city}"

    class Meta:
        verbose_name = _("location")
        verbose_name_plural = _("locations")
        ordering = ["name"]

    def get_absolute_url(self):
        try:
            site = current_site()
            if not self.section or site == self.section.site:
                return reverse_app([f"{site.pk}-events"],
                                   "location-detail",
                                   kwargs={"slug": self.slug})
            with set_current_site(self.section.site):
                return ("//" + self.section.site.host + reverse_app(
                    [f"{self.section.site.id}-events"],
                    "location-detail",
                    urlconf=apps_urlconf(),
                    kwargs={"slug": self.slug},
                ))
        except NoReverseMatch:
            return "#"
Exemple #14
0
class EmailMessage(models.Model):
    ORDER_CHOICES = (('created', 'Oldest first'), ('-created', 'Newest first'),
                     ('?', 'Random'))

    regions = [
        Region(key='body', title='Main Body'),
        # Region(key='split_test', title='Split Test Body',
        #        inherited=False),
    ]
    name = models.CharField(max_length=255,
                            unique=True,
                            verbose_name='Email Message Name',
                            help_text='A unique name for this email message.')

    enabled = models.BooleanField(default=False)

    note = models.TextField(max_length=255,
                            null=True,
                            blank=True,
                            help_text="This is only seen by staff.")

    from_email = models.EmailField(null=True,
                                   blank=True,
                                   help_text='Set a custom from email.')
    from_email_name = models.CharField(
        max_length=150,
        null=True,
        blank=True,
        help_text="Set a name for a custom from email.")
    message_class = models.CharField(max_length=120,
                                     blank=True,
                                     default='default')
    slice = models.IntegerField(
        null=True,
        blank=True,
        help_text=
        'Send to only a specific amount of subscribers. Useful for testing to a portion of the list. A value of 1000 would send to 1000 subscribers each time this EmailMessage is sent out.'
    )
    subscriber_order = models.CharField(
        max_length=25,
        choices=ORDER_CHOICES,
        default='created',
        help_text='Send out to subscribers in this order.')
    send_after = models.DateTimeField(blank=True, null=True)
    disable_after_broadcast = models.BooleanField(
        default=False,
        help_text=
        "Useful with 'send after' broadcasts. After broadcast is initiated, disable this drip so it won't try to go out again."
    )
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        app_label = 'squeezemail'

    def handler(self, *args, **kwargs):
        kwargs['email_message_model'] = self
        handler_class = class_for(SQUEEZE_EMAILMESSAGE_HANDLER)
        handler = handler_class(*args, **kwargs)
        return handler

    def __str__(self):
        return self.name

    @cached_property
    def lock_id(self):
        # example lock id: prefix_email_message_1
        return str(SQUEEZE_PREFIX).encode('utf-8') + 'email_message_'.encode(
            'utf-8') + str(self.id).encode('utf-8')

    def acquire_lock(self):
        return cache.add(self.lock_id, 'true', LOCK_EXPIRE)

    def release_lock(self):
        return cache.delete(self.lock_id)

    @cached_property
    def subject(self):
        return self.choose_split_test_subject.text

    @cached_property
    def get_split_test_subjects(self):
        return self.subjects.filter(enabled=True)

    @cached_property
    def split_subject_active(self):
        return self.get_split_test_subjects.count() > 1

    @cached_property
    def choose_split_test_subject(self):
        # Return a subject object to be able to get the subject text and the subject id
        random_subject = self.subjects.filter(enabled=True).order_by('?')[0]
        return random_subject

    def get_split_test_body(self):
        pass

    def step_run(self, calling_step, subscription_qs):
        subscriber_qs = Subscriber.objects.filter(
            id__in=subscription_qs.values_list('subscriber_id', flat=True))

        successfully_sent_subscriber_qs = self.handler(
            step=calling_step,
            queryset=subscriber_qs).step_run(calling_step, subscription_qs)
        # assert False, successfully_sent_subscriber_qs
        return successfully_sent_subscriber_qs

    def apply_queryset_rules(self, qs):
        """
        First collect all filter/exclude kwargs and apply any annotations.
        Then apply all filters at once, and all excludes at once.
        """
        clauses = {'filter': [], 'exclude': []}

        for rule in self.queryset_rules.all():

            clause = clauses.get(rule.method_type, clauses['filter'])

            kwargs = rule.filter_kwargs(qs)
            clause.append(Q(**kwargs))

            qs = rule.apply_any_annotation(qs)

        if clauses['exclude']:
            qs = qs.exclude(functools.reduce(operator.or_, clauses['exclude']))
        qs = qs.filter(*clauses['filter'])
        return qs

    @cached_property
    def total_sent(self):
        return self.sent_email_messages.all().count()

    @cached_property
    def total_unique_opens(self):
        return Open.objects.filter(
            sent_email_message__email_message_id=self.pk).count()

    @cached_property
    def total_opens(self):
        return Open.objects.filter(
            sent_email_message__email_message_id=self.pk).aggregate(
                Sum('total'))['total__sum'] or 0

    @cached_property
    def total_unique_clicks(self):
        return Click.objects.filter(
            sent_email_message__email_message_id=self.pk).count()

    @cached_property
    def total_clicks(self):
        return Click.objects.filter(
            sent_email_message__email_message_id=self.pk).aggregate(
                Sum('total'))['total__sum'] or 0

    @cached_property
    def total_unsubscribes(self):
        return Unsubscribe.objects.filter(
            sent_email_message__email_message_id=self.pk).count()

    @cached_property
    def total_bounces(self):
        return Bounce.objects.filter(
            sent_email_message__email_message_id=self.pk).count()

    @cached_property
    def total_spammed(self):
        return Spam.objects.filter(
            sent_email_message__email_message_id=self.pk).count()

    def unique_open_rate(self):
        total_sent = self.total_sent
        total_opened = self.total_unique_opens
        if total_opened and total_sent > 0 and total_opened > 0:
            return "{0:.2f}%".format((total_opened / total_sent) * 100.0)
        return 0

    def open_rate(self):
        total_sent = self.total_sent
        total_opened = self.total_opens
        if total_opened and total_sent > 0 and total_opened > 0:
            return "{0:.2f}%".format((total_opened / total_sent) * 100.0)
        return 0

    def unique_click_rate(self):
        total_sent = self.total_sent
        total_clicked = self.total_unique_clicks
        if total_clicked and total_sent > 0 and total_clicked > 0:
            return "{0:.2f}%".format((total_clicked / total_sent) * 100.0)
        return 0

    def click_rate(self):
        total_sent = self.total_sent
        total_clicked = self.total_clicks
        if total_clicked and total_sent > 0 and total_clicked > 0:
            return "{0:.2f}%".format((total_clicked / total_sent) * 100.0)
        return 0

    def unique_click_to_open_rate(self):
        """
        Click to open rate is the percentage of recipients who opened
        the email message and also clicked on any link in the email message.
        """
        total_opened = self.total_opens
        total_clicked = self.total_unique_clicks
        if total_clicked and total_opened > 0 and total_clicked > 0:
            return "{0:.2f}%".format((total_clicked / total_opened) * 100.0)
        return 0

    def click_to_open_rate(self):
        """
        Click to open rate is the percentage of recipients who opened
        the email message and also clicked on any link in the email message.
        """
        total_opened = self.total_opens
        total_clicked = self.total_clicks
        if total_clicked and total_opened > 0 and total_clicked > 0:
            return "{0:.2f}%".format((total_clicked / total_opened) * 100.0)
        return 0

    def bounce_rate(self):
        total_sent = self.total_sent
        total_bounced = self.total_bounces
        if total_sent > 0 and total_bounced > 0:
            return "{0:.2f}%".format((total_bounced / total_sent) * 100.0)
        return 0

    def unsubscribe_rate(self):
        total_sent = self.total_sent
        total_unsubscribed = self.total_unsubscribes
        if total_sent > 0 and total_unsubscribed > 0:
            return "{0:.2f}%".format((total_unsubscribed / total_sent) * 100.0)
        return 0

    def spam_rate(self):
        total_sent = self.total_sent
        total_spammed = self.total_spammed
        if total_sent > 0 and total_spammed > 0:
            return "{0:.2f}%".format((total_spammed / total_sent) * 100.0)
        return 0

    def disable(self):
        self.enabled = False
        return self.save()
Exemple #15
0
class Event(models.Model):
    """Model representing a CompClub event."""

    name = models.CharField(max_length=100)
    start_date = models.DateField(
        help_text=
        "Users without the view_unreleased_event permission cannot see the event until start date and the event is released."
    )  # noqa: E501
    finish_date = models.DateField()
    owner = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
    slug = models.SlugField(
        default='event',
        unique=False,
        help_text=
        "The slug is the what appears in the URL. If you leave it as the default the slug will be autogenerated."
    )  # noqa: E501
    description = models.TextField(null="", default="")
    display_image = models.ImageField(
        upload_to='uploads/%Y/%m/',
        max_length=150,
        null=True,
        blank=True,
        default="",
        help_text=
        "This field is optional. Please provide it if you are highlighting the event."
    )  # noqa: E501
    highlighted_event = models.BooleanField(
        default=False, help_text="Display this event on the homepage")
    hidden_event = models.BooleanField(
        default=True,
        help_text=
        "Users cannot view the event (including on the events feed)",  # noqa: E501
    )
    released = models.BooleanField(
        default=True,
        help_text=
        "Leave this checked if you want to automatically release the event on the start date (at midnight).",  # noqa: E501
    )

    regions = [Region(key='main', title='main region')]

    class Meta:  # noqa: D106
        permissions = [
            ("view_hidden_event", "Can view hidden events"),
            ("view_unreleased_event", "Can view unreleased events"),
        ]

    def __str__(self):
        """Return a string representation of an event."""
        return self.name

    def clean(self):
        """Restrict highlighting to events with display images only."""
        if self.highlighted_event and self.hidden_event:
            raise ValidationError(
                'You can\'t highlight an event and hide it at the same time.')
        if self.highlighted_event and not self.display_image:
            raise ValidationError(
                'You must provide a display image if highlighting the event.')

    def save(self, *args, **kwargs):
        """Override save to update slug."""
        self.slug = slugify(self.name)
        if self.display_image and str(self.display_image.path) != str(
                self.display_image.file):
            self.display_image = compressors.compress_image(self.display_image)

        super(Event, self).save(*args, **kwargs)