Exemple #1
0
class Category(models.Model, metaclass=ModelTranslateMeta):
    """Describes a Message category"""

    # These should be the keys of the categories that we automatically created
    # in the migrations (0012 to be specific)
    GENERAL = "general"
    PIZZA = "pizza"
    EVENT = "event"
    NEWSLETTER = "newsletter"
    PARTNER = "partner"
    PHOTO = "photo"
    BOARD = "board"

    key = models.CharField(max_length=16, primary_key=True)

    name = MultilingualField(
        models.CharField,
        _("name"),
        max_length=32,
    )

    description = MultilingualField(models.TextField,
                                    _("description"),
                                    default="")

    def __str__(self):
        return self.name_en
Exemple #2
0
class MerchandiseItem(models.Model, metaclass=ModelTranslateMeta):
    """
    Merchandise items

    This model describes merchandise items.
    """

    #: Name of the merchandise item.
    name = MultilingualField(models.CharField, max_length=200)

    #: Price of the merchandise item
    price = models.DecimalField(max_digits=5, decimal_places=2)

    #: Description of the merchandise item
    description = MultilingualField(models.TextField)

    #: Image of the merchandise item
    image = models.ImageField(upload_to="public/merchandise")

    def __str__(self):
        """Gives the name of the merchandise item in the currently
        active locale.

        :return: The name of the merchandise item.
        :rtype: str
        """
        return str(self.name)
Exemple #3
0
class NewsletterContent(models.Model, metaclass=ModelTranslateMeta):
    """Describes one piece of basic content of a newsletter"""

    title = MultilingualField(
        models.CharField,
        max_length=150,
        verbose_name=_("Title"),
        blank=False,
        null=False,
    )

    url = models.URLField(
        verbose_name=_("URL"),
        blank=True,
        null=True,
        help_text=_("If filled, it will make the title a link to this URL"),
    )

    description = MultilingualField(
        HTMLField,
        verbose_name=_("Description"),
        blank=False,
        null=False,
    )

    newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE)

    order = models.PositiveIntegerField(verbose_name=_("order"),
                                        blank=False,
                                        null=True,
                                        default=0)

    def clean(self):
        super().clean()

        errors = {}
        url = "admin/newsletters/"
        if url in self.description_nl:
            errors.update({
                "description_nl":
                _("Please make sure all urls are absolute "
                  "and start with http(s)://.")
            })
        if url in self.description_en:
            errors.update({
                "description_en":
                _("Please make sure all urls are absolute "
                  "and start with http(s)://.")
            })

        if errors:
            raise ValidationError(errors)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ("order", )
Exemple #4
0
class PartnerEvent(models.Model, metaclass=ModelTranslateMeta):
    """Model describing partner event."""

    partner = models.ForeignKey(
        Partner,
        verbose_name=_("partner"),
        on_delete=models.CASCADE,
        related_name="events",
        blank=True,
        null=True,
    )

    other_partner = models.CharField(max_length=255, blank=True)

    title = MultilingualField(models.CharField, _("title"), max_length=100)

    description = MultilingualField(models.TextField, _("description"))

    location = MultilingualField(
        models.CharField,
        _("location"),
        max_length=255,
    )

    start = models.DateTimeField(_("start time"))

    end = models.DateTimeField(_("end time"))

    url = models.URLField(_("website"))

    published = models.BooleanField(_("published"), default=False)

    def clean(self):
        """Validate the partner event."""
        super().clean()
        errors = {}
        if (not self.partner
                and not self.other_partner) or (self.partner
                                                and self.other_partner):
            errors.update({
                "partner":
                _("Please select or enter "
                  "a partner for this event."),
                "other_partner":
                _("Please select or enter "
                  "a partner for this event."),
            })

        if errors:
            raise ValidationError(errors)

    def __str__(self):
        """Return the event title."""
        return self.title
Exemple #5
0
class Document(models.Model, metaclass=ModelTranslateMeta):
    """Describes a base document"""
    class Meta:
        verbose_name = _("Document")
        verbose_name_plural = _("Documents")

    DOCUMENT_CATEGORIES = (
        ("annual", _("Annual document")),
        ("association", _("Association document")),
        ("event", _("Event document")),
        ("minutes", _("Minutes")),
        ("misc", _("Miscellaneous document")),
    )

    name = MultilingualField(models.CharField,
                             verbose_name=_("name"),
                             max_length=200)

    created = models.DateTimeField(
        verbose_name=_("created"),
        auto_now_add=True,
    )

    last_updated = models.DateTimeField(verbose_name=_("last updated"),
                                        auto_now=True)

    category = models.CharField(
        max_length=40,
        choices=DOCUMENT_CATEGORIES,
        verbose_name=_("category"),
        default="misc",
    )

    file = MultilingualField(
        models.FileField,
        verbose_name=_("file"),
        upload_to="documents/",
        validators=[
            FileExtensionValidator(["txt", "pdf", "jpg", "jpeg", "png"])
        ],
    )

    members_only = models.BooleanField(verbose_name=_("members only"),
                                       default=False)

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

    def __str__(self):
        return "%s (%s)" % (self.name, str(self.created.date()))
Exemple #6
0
class Course(models.Model, metaclass=ModelTranslateMeta):
    """Describes a course"""

    name = MultilingualField(models.CharField, max_length=255)

    categories = models.ManyToManyField(Category,
                                        verbose_name=_("categories"),
                                        blank=True)

    old_courses = models.ManyToManyField("self",
                                         symmetrical=False,
                                         verbose_name=_("old courses"),
                                         blank=True)

    course_code = models.CharField(max_length=16)

    ec = models.IntegerField(verbose_name=_("EC"))

    since = models.IntegerField()
    until = models.IntegerField(blank=True, null=True)

    def __str__(self):
        return "{} ({})".format(self.name, self.course_code)

    def get_absolute_url(self):
        return reverse("education:course", args=[str(self.pk)])

    class Meta:
        ordering = ["-pk"]
        verbose_name = _("course")
        verbose_name_plural = _("courses")
Exemple #7
0
class FrontpageArticle(models.Model, metaclass=ModelTranslateMeta):
    """Front page articles"""

    title = MultilingualField(
        models.CharField,
        verbose_name=_("Title"),
        help_text=_("The title of the article; what goes in the header"),
        blank=False,
        max_length=80,
    )

    content = MultilingualField(
        HTMLField,
        verbose_name=_("Content"),
        help_text=_("The content of the article; what text to display."),
        blank=False,
        max_length=5000,
    )

    since = models.DateTimeField(
        verbose_name=_("Display since"),
        help_text=_("Hide this article before this time."),
        default=timezone.now,
    )

    until = models.DateTimeField(
        verbose_name=_("Display until"),
        help_text=_("Hide this article after this time."),
        blank=True,
        null=True,
    )

    class Meta:
        ordering = ("-since", )

    def __str__(self):
        return self.title

    @property
    def is_visible(self):
        """Is this announcement currently visible"""
        return (self.until is None or self.until > timezone.now()) and (
            self.since is None or self.since <= timezone.now())
Exemple #8
0
class NewsletterEvent(NewsletterContent):
    """Describes one piece of event content of a newsletter"""

    where = MultilingualField(
        models.CharField,
        max_length=150,
        verbose_name=_("Where"),
        blank=False,
        null=False,
    )

    start_datetime = models.DateTimeField(
        verbose_name=_("Start date and time"), blank=False, null=False,
    )

    end_datetime = models.DateTimeField(
        verbose_name=_("End date and time"), blank=False, null=False,
    )

    show_costs_warning = models.BooleanField(
        verbose_name=_("Show warnings about costs"), default=True
    )

    price = models.DecimalField(
        verbose_name=_("Price (in Euro)"),
        max_digits=5,
        decimal_places=2,
        blank=True,
        null=True,
        default=None,
    )

    penalty_costs = models.DecimalField(
        verbose_name=_("Costs (in Euro)"),
        max_digits=5,
        decimal_places=2,
        blank=True,
        null=True,
        default=None,
        help_text=_(
            "This is the price that a member has to pay when he/she did not show up."
        ),
    )

    def clean(self):
        """Make sure that the event end date is after the start date"""
        super().clean()
        if (
            self.end_datetime is not None
            and self.start_datetime is not None
            and self.end_datetime < self.start_datetime
        ):
            raise ValidationError(
                {"end_datetime": _("Can't have an event travel back in time")}
            )
Exemple #9
0
class VacancyCategory(models.Model, metaclass=ModelTranslateMeta):
    """Model describing vacancy categories."""

    name = MultilingualField(models.CharField, max_length=30)
    slug = models.SlugField()

    def __str__(self):
        """Return the category name."""
        return str(self.name)

    class Meta:
        """Meta class for vacancy category model."""

        verbose_name_plural = _("Vacancy Categories")
Exemple #10
0
class Category(models.Model, metaclass=ModelTranslateMeta):
    """Describes a course category"""

    name = MultilingualField(
        models.CharField,
        max_length=64,
    )

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("education:category", args=[str(self.pk)])

    class Meta:
        verbose_name = _("category")
        verbose_name_plural = _("categories")
Exemple #11
0
class Announcement(models.Model, metaclass=ModelTranslateMeta):
    """Describes an announcement"""

    content = MultilingualField(
        HTMLField,
        verbose_name=_("Content"),
        help_text=_("The content of the announcement; what text to display."),
        blank=False,
        max_length=500,
    )

    since = models.DateTimeField(
        verbose_name=_("Display since"),
        help_text=_("Hide this announcement before this time."),
        default=timezone.now,
    )

    until = models.DateTimeField(
        verbose_name=_("Display until"),
        help_text=_("Hide this announcement after this time."),
        blank=True,
        null=True,
    )

    icon = models.CharField(
        verbose_name=_("Font Awesome icon"),
        help_text=_("Font Awesome abbreviation for icon to use."),
        max_length=150,
        default="bullhorn",
    )

    closeable = models.BooleanField(default=True)

    class Meta:
        ordering = ("-since", )

    def __str__(self):
        return self.content

    @property
    def is_visible(self):
        """Is this announcement currently visible"""
        return (self.until is None or self.until > timezone.now()) and (
            self.since is None or self.since <= timezone.now())
Exemple #12
0
class GeneralMeeting(models.Model, metaclass=ModelTranslateMeta):
    """Describes a general meeting"""
    class Meta:
        verbose_name = _("General meeting")
        verbose_name_plural = _("General meetings")
        ordering = ["datetime"]

    documents = models.ManyToManyField(
        Document,
        verbose_name=_("documents"),
        blank=True,
    )

    datetime = models.DateTimeField(verbose_name=_("datetime"), )

    location = MultilingualField(models.CharField,
                                 verbose_name=_("location"),
                                 max_length=200)

    def __str__(self):
        return timezone.localtime(self.datetime).strftime("%Y-%m-%d")
Exemple #13
0
class Product(models.Model, metaclass=ModelTranslateMeta):
    """Describes a product"""

    objects = models.Manager()
    available_products = AvailableProductManager()

    name = models.CharField(max_length=50)
    description = MultilingualField(models.TextField)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    available = models.BooleanField(default=True)
    restricted = models.BooleanField(
        default=False,
        help_text=_("Only allow to be ordered by people with the "
                    "'order restricted products' permission."),
    )

    def __str__(self):
        return self.name

    class Meta:
        ordering = ("name", )
        permissions = (("order_restricted_products",
                        _("Order restricted products")), )
Exemple #14
0
class Message(models.Model, metaclass=ModelTranslateMeta):
    """Describes a push notification"""

    objects = NormalMessageManager()
    all_objects = MessageManager()

    users = models.ManyToManyField(settings.AUTH_USER_MODEL)
    title = MultilingualField(models.CharField,
                              max_length=150,
                              verbose_name=_("title"))
    body = MultilingualField(models.TextField, verbose_name=_("body"))
    url = models.CharField(
        verbose_name=_("url"),
        max_length=256,
        null=True,
        blank=True,
    )
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        verbose_name=_("category"),
        default="general",
    )
    sent = models.DateTimeField(
        verbose_name=_("sent"),
        null=True,
    )
    failure = models.IntegerField(
        verbose_name=_("failure"),
        blank=True,
        null=True,
    )
    success = models.IntegerField(
        verbose_name=_("success"),
        blank=True,
        null=True,
    )

    def __str__(self):
        return "{}: {}".format(self.title, self.body)

    def send(self, **kwargs):
        if self:
            success_total = 0
            failure_total = 0
            ttl = kwargs.get("ttl", 3600)

            for lang in settings.LANGUAGES:
                with override(lang[0]):
                    reg_ids = list(
                        Device.objects.filter(
                            user__in=self.users.all(),
                            receive_category__key=self.category_id,
                            active=True,
                            language=lang[0],
                        ).values_list("registration_id", flat=True))

                    data = kwargs.get("data", {})
                    if self.url is not None:
                        data["url"] = self.url
                    data["title"] = self.title
                    data["body"] = str(self.body)

                    message = messaging.Message(
                        notification=messaging.Notification(
                            title=data["title"],
                            body=data["body"],
                        ),
                        data=data,
                        android=messaging.AndroidConfig(
                            ttl=datetime.timedelta(seconds=ttl),
                            priority="normal",
                            notification=messaging.AndroidNotification(
                                color="#E62272",
                                sound="default",
                            ),
                        ),
                    )

                    for reg_id in reg_ids:
                        message.token = reg_id
                        try:
                            messaging.send(message,
                                           dry_run=kwargs.get(
                                               "dry_run", False))
                            success_total += 1
                        except messaging.UnregisteredError:
                            failure_total += 1
                            Device.objects.filter(
                                registration_id=reg_id).delete()
                        except exceptions.InvalidArgumentError:
                            failure_total += 1
                            Device.objects.filter(
                                registration_id=reg_id).update(active=False)
                        except exceptions.FirebaseError:
                            failure_total += 1

            self.sent = timezone.now()
            self.success = success_total
            self.failure = failure_total
            self.save()
Exemple #15
0
class MemberGroupMembership(models.Model, metaclass=ModelTranslateMeta):
    """Describes a group membership"""

    objects = models.Manager()
    active_objects = ActiveMembershipManager()

    member = models.ForeignKey(
        "members.Member",
        on_delete=models.CASCADE,
        verbose_name=_("Member"),
    )

    group = models.ForeignKey(
        MemberGroup,
        on_delete=models.CASCADE,
        verbose_name=_("Group"),
    )

    since = models.DateField(
        verbose_name=_("Member since"),
        help_text=_("The date this member joined in this role"),
        default=datetime.date.today,
    )

    until = models.DateField(
        verbose_name=_("Member until"),
        help_text=_("A member until this time "
                    "(can't be in the future)."),
        blank=True,
        null=True,
    )

    chair = models.BooleanField(
        verbose_name=_("Chair of the group"),
        help_text=_("There can only be one chair at a time!"),
        default=False,
    )

    role = MultilingualField(
        models.CharField,
        _("role"),
        help_text=_("The role of this member"),
        max_length=255,
        blank=True,
        null=True,
    )

    @property
    def initial_connected_membership(self):
        """Find the oldest membership directly connected to the current one"""
        qs = MemberGroupMembership.objects.filter(
            group=self.group,
            member=self.member,
            until__lte=self.since,
            until__gte=self.since - datetime.timedelta(days=1),
        )
        if qs.count() >= 1:  # should only be one; should be unique
            return qs.first().initial_connected_membership
        else:
            return self

    @property
    def latest_connected_membership(self):
        """
        Find the newest membership directly connected to the current one
        (thus the membership that started at the moment the current one ended)
        """
        if self.until:
            qs = MemberGroupMembership.objects.filter(
                group=self.group,
                member=self.member,
                since__lte=self.until,
                since__gte=self.until + datetime.timedelta(days=1),
            )
            if qs.count() >= 1:  # should only be one; should be unique
                return qs.last().latest_connected_membership
        return self

    @property
    def is_active(self):
        """Is this membership currently active"""
        return self.until is None or self.until > timezone.now().date()

    def clean(self):
        try:
            if self.until and (not self.since or self.until < self.since):
                raise ValidationError(
                    {"until": _("End date can't be before start date")})
            if self.until and self.until > timezone.now().date():
                raise ValidationError(
                    {"until": _("End date can't be in the future")})
            if self.since and self.group.since and self.since < self.group.since:
                raise ValidationError({
                    "since":
                    _("Start date can't be before group start date")
                })
            if self.since and self.group.until and self.since > self.group.until:
                raise ValidationError(
                    {"since": _("Start date can't be after group end date")})
        except MemberGroupMembership.group.RelatedObjectDoesNotExist:
            return False

    def validate_unique(self, *args, **kwargs):
        try:
            super().validate_unique(*args, **kwargs)
            # Check if a group has more than one chair
            if self.chair:
                chairs = MemberGroupMembership.objects.filter(group=self.group,
                                                              chair=True)
                for chair in chairs:
                    if chair.pk == self.pk:
                        continue
                    if ((chair.until is None and
                         (self.until is None or self.until > chair.since)) or
                        (self.until is None and self.since < chair.until)
                            or (self.until and chair.until
                                and self.since < chair.until
                                and self.until > chair.since)):
                        raise ValidationError({
                            NON_FIELD_ERRORS:
                            _("There already is a "
                              "chair for this time period")
                        })

            # check if this member is already in the group in this period
            memberships = MemberGroupMembership.objects.filter(
                group=self.group, member=self.member)
            for mship in memberships:
                if mship.pk == self.pk:
                    continue
                if ((mship.until is None and
                     (self.until is None or self.until > mship.since))
                        or (self.until is None and self.since < mship.until) or
                    (self.until and mship.until and self.since < mship.until
                     and self.until > mship.since)):
                    raise ValidationError({
                        "member":
                        _("This member is already in the group for "
                          "this period")
                    })
        except (
                MemberGroupMembership.member.RelatedObjectDoesNotExist,
                MemberGroupMembership.group.RelatedObjectDoesNotExist,
        ):
            return False

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        self.member.is_staff = (self.member.membergroupmembership_set.exclude(
            until__lte=timezone.now().date()).count()) >= 1
        self.member.save()

    def __str__(self):
        return _("{member} membership of {group} "
                 "since {since}, until {until}").format(member=self.member,
                                                        group=self.group,
                                                        since=self.since,
                                                        until=self.until)

    class Meta:
        verbose_name = _("group membership")
        verbose_name_plural = _("group memberships")
Exemple #16
0
class MemberGroup(models.Model, metaclass=ModelTranslateMeta):
    """Describes a groups of members"""

    objects = models.Manager()
    active_objects = ActiveMemberGroupManager()

    name = MultilingualField(
        models.CharField,
        max_length=40,
        verbose_name=_("Name"),
        unique=True,
    )

    description = MultilingualField(
        HTMLField,
        verbose_name=_("Description"),
    )

    photo = models.ImageField(
        verbose_name=_("Image"),
        upload_to="public/committeephotos/",
        null=True,
        blank=True,
    )

    members = models.ManyToManyField(
        "members.Member", through="activemembers.MemberGroupMembership")

    permissions = models.ManyToManyField(
        Permission,
        verbose_name=_("permissions"),
        blank=True,
    )

    since = models.DateField(
        _("founded in"),
        null=True,
        blank=True,
    )

    until = models.DateField(
        _("existed until"),
        null=True,
        blank=True,
    )

    active = models.BooleanField(
        default=False,
        help_text=_("This should only be unchecked if the committee has been "
                    "dissolved. The websites assumes that any committees on it"
                    " existed at some point."),
    )

    contact_email = models.EmailField(
        _("contact email address"),
        blank=True,
        null=True,
    )

    contact_mailinglist = models.OneToOneField(
        "mailinglists.MailingList",
        verbose_name=_("contact mailing list"),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    display_members = models.BooleanField(default=False, )

    @property
    def contact_address(self):
        if self.contact_mailinglist:
            return f"{self.contact_mailinglist.name}@{settings.SITE_DOMAIN}"
        return self.contact_email

    def clean(self):
        if (self.contact_email is not None and self.contact_mailinglist
                is not None) or (self.contact_email is None
                                 and self.contact_mailinglist is None):
            raise ValidationError({
                "contact_email":
                _("Please use either the mailing list "
                  "or email address option."),
                "contact_mailinglist":
                _("Please use either the mailing list "
                  "or email address option."),
            })

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        try:
            return self.board.get_absolute_url()
        except self.DoesNotExist:
            try:
                return self.committee.get_absolute_url()
            except self.DoesNotExist:
                try:
                    return self.society.get_absolute_url()
                except self.DoesNotExist:
                    pass

    class Meta:
        verbose_name = _("member group")
        verbose_name_plural = _("member groups")
Exemple #17
0
class Newsletter(models.Model, metaclass=ModelTranslateMeta):
    """Describes a newsletter"""

    title = MultilingualField(
        models.CharField,
        max_length=150,
        verbose_name=_("Title"),
        help_text=_("The title is used for the email subject."),
        blank=False,
    )

    date = models.DateField(
        verbose_name=_("Date"),
        help_text=_(
            "This date is used to extract the week of this "
            "newsletter, best scenario:"
            "always use the monday of the week the newsletter is "
            "for. If you leave it empty no week is shown."
        ),
        blank=True,
        null=True,
    )

    send_date = models.DateTimeField(
        verbose_name=_("Send date"), blank=True, null=True,
    )

    description = MultilingualField(
        HTMLField,
        verbose_name=_("Introduction"),
        help_text=_(
            "This is the text that starts the newsletter. It always "
            'begins with "Dear members" and you can append '
            "whatever you want."
        ),
        blank=False,
    )

    sent = models.BooleanField(default=False)

    def get_absolute_url(self):
        return reverse("newsletters:preview", args=(self.pk,))

    def clean(self):
        super().clean()

        errors = {}
        url = "admin/newsletters/"
        if url in self.description_en:
            errors.update(
                {
                    "description_en": _(
                        "Please make sure all urls are absolute "
                        "and contain http(s)://."
                    )
                }
            )
        if self.send_date and self.send_date <= timezone.now():
            errors.update(
                {"send_date": _("Please make sure the send date is not in the past.")}
            )

        if errors:
            raise ValidationError(errors)

    class Meta:
        permissions = (("send_newsletter", "Can send newsletter"),)

    def __str__(self):
        return str(self.title)
Exemple #18
0
class Album(models.Model, metaclass=ModelTranslateMeta):
    title = MultilingualField(
        models.CharField,
        _("title"),
        max_length=200,
    )

    dirname = models.CharField(
        verbose_name=_("directory name"),
        max_length=200,
    )

    date = models.DateField(verbose_name=_("date"), )

    slug = models.SlugField(
        verbose_name=_("slug"),
        unique=True,
    )

    hidden = models.BooleanField(verbose_name=_("hidden"), default=False)

    new_album_notification = models.ForeignKey(
        ScheduledMessage,
        on_delete=models.deletion.SET_NULL,
        blank=True,
        null=True)

    _cover = models.OneToOneField(
        Photo,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name="covered_album",
        verbose_name=_("cover image"),
    )

    shareable = models.BooleanField(verbose_name=_("shareable"), default=False)

    photosdir = "photos"
    photospath = os.path.join(settings.MEDIA_ROOT, photosdir)

    @cached_property
    def cover(self):
        cover = None
        if self._cover is not None:
            return self._cover
        elif self.photo_set.exists():
            random.seed(self.dirname)
            cover = random.choice(self.photo_set.all())
        return cover

    def __str__(self):
        return "{} {}".format(self.date.strftime("%Y-%m-%d"), self.title)

    def get_absolute_url(self):
        return reverse("photos:album", args=[str(self.slug)])

    def save(self, *args, **kwargs):
        # dirname is only set for new objects, to avoid ever changing it
        if self.pk is None:
            self.dirname = self.slug

        if not self.hidden and (self.new_album_notification is None
                                or not self.new_album_notification.sent):
            new_album_notification_time = timezone.now() + timezone.timedelta(
                hours=1)
            new_album_notification = ScheduledMessage()

            if (self.new_album_notification is not None
                    and not self.new_album_notification.sent):
                new_album_notification = self.new_album_notification

            new_album_notification.title_en = "New album uploaded"
            new_album_notification.title_nl = "Nieuw album geüpload"
            new_album_notification.body_en = ("A new photo album '{}' has "
                                              "just been uploaded".format(
                                                  self.title_en))
            new_album_notification.body_nl = ("Een nieuw fotoalbum '{}' is "
                                              "zojuist geüpload".format(
                                                  self.title_nl))
            new_album_notification.category = Category.objects.get(
                key=Category.PHOTO)
            new_album_notification.url = (f"{settings.BASE_URL}"
                                          f"{self.get_absolute_url()}")
            new_album_notification.time = new_album_notification_time
            new_album_notification.save()
            self.new_album_notification = new_album_notification
            self.new_album_notification.users.set(Member.current_members.all())
        elif (self.hidden and self.new_album_notification is not None
              and not self.new_album_notification.sent):
            existing_notification = self.new_album_notification
            self.new_album_notification = None
            existing_notification.delete()

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

    @property
    def access_token(self):
        return hashlib.sha256("{}album{}".format(
            settings.SECRET_KEY, self.pk).encode("utf-8")).hexdigest()

    class Meta:
        ordering = ("-date", "title_en")
Exemple #19
0
 class _TestItem5(models.Model,
                  metaclass=ModelTranslateMeta):
     foreign = MultilingualField(field_type, "TestItem5")
Exemple #20
0
 class _TestItem2(models.Model, metaclass=ModelTranslateMeta):
     text = MultilingualField(models.TextField, verbose_name="Text")
Exemple #21
0
 class _TestItem3b(models.Model, metaclass=ModelTranslateMeta):
     text = MultilingualField(models.TextField)
Exemple #22
0
class RegistrationInformationField(models.Model, metaclass=ModelTranslateMeta):
    """Describes a field description to ask for when registering"""

    BOOLEAN_FIELD = "boolean"
    INTEGER_FIELD = "integer"
    TEXT_FIELD = "text"

    FIELD_TYPES = (
        (BOOLEAN_FIELD, _("Checkbox")),
        (TEXT_FIELD, _("Text")),
        (INTEGER_FIELD, _("Integer")),
    )

    event = models.ForeignKey(Event, models.CASCADE)

    type = models.CharField(
        _("field type"),
        choices=FIELD_TYPES,
        max_length=10,
    )

    name = MultilingualField(
        models.CharField,
        _("field name"),
        max_length=100,
    )

    description = MultilingualField(
        models.TextField,
        _("description"),
        null=True,
        blank=True,
    )

    required = models.BooleanField(_("required"), )

    def get_value_for(self, registration):
        if self.type == self.TEXT_FIELD:
            value_set = self.textregistrationinformation_set
        elif self.type == self.BOOLEAN_FIELD:
            value_set = self.booleanregistrationinformation_set
        elif self.type == self.INTEGER_FIELD:
            value_set = self.integerregistrationinformation_set

        try:
            return value_set.get(registration=registration).value
        except (
                TextRegistrationInformation.DoesNotExist,
                BooleanRegistrationInformation.DoesNotExist,
                IntegerRegistrationInformation.DoesNotExist,
        ):
            return None

    def set_value_for(self, registration, value):
        if self.type == self.TEXT_FIELD:
            value_set = self.textregistrationinformation_set
        elif self.type == self.BOOLEAN_FIELD:
            value_set = self.booleanregistrationinformation_set
        elif self.type == self.INTEGER_FIELD:
            value_set = self.integerregistrationinformation_set

        try:
            field_value = value_set.get(registration=registration)
        except BooleanRegistrationInformation.DoesNotExist:
            field_value = BooleanRegistrationInformation()
        except TextRegistrationInformation.DoesNotExist:
            field_value = TextRegistrationInformation()
        except IntegerRegistrationInformation.DoesNotExist:
            field_value = IntegerRegistrationInformation()

        field_value.registration = registration
        field_value.field = self
        field_value.value = value
        field_value.save()

    def __str__(self):
        return "{} ({})".format(self.name, dict(self.FIELD_TYPES)[self.type])

    class Meta:
        order_with_respect_to = "event"
Exemple #23
0
 class _TestItem4(models.Model, metaclass=ModelTranslateMeta):
     text = MultilingualField(models.CharField, "Text", max_length=100)
Exemple #24
0
class Event(models.Model, metaclass=ModelTranslateMeta):
    """Describes an event"""

    CATEGORY_ALUMNI = "alumni"
    CATEGORY_EDUCATION = "education"
    CATEGORY_CAREER = "career"
    CATEGORY_LEISURE = "leisure"
    CATEGORY_ASSOCIATION = "association"
    CATEGORY_OTHER = "other"

    EVENT_CATEGORIES = (
        (CATEGORY_ALUMNI, _("Alumni")),
        (CATEGORY_EDUCATION, _("Education")),
        (CATEGORY_CAREER, _("Career")),
        (CATEGORY_LEISURE, _("Leisure")),
        (CATEGORY_ASSOCIATION, _("Association Affairs")),
        (CATEGORY_OTHER, _("Other")),
    )

    DEFAULT_NO_REGISTRATION_MESSAGE = _("No registration required / "
                                        "Geen aanmelding vereist")

    title = MultilingualField(models.CharField, _("title"), max_length=100)

    description = MultilingualField(
        HTMLField,
        _("description"),
        help_text=_("Please fill in both of the description boxes (EN/NL),"
                    " even if your event is Dutch only! Fill in the English "
                    "description in Dutch then."),
    )

    start = models.DateTimeField(_("start time"))

    end = models.DateTimeField(_("end time"))

    organiser = models.ForeignKey("activemembers.MemberGroup",
                                  models.PROTECT,
                                  verbose_name=_("organiser"))

    category = models.CharField(
        max_length=40,
        choices=EVENT_CATEGORIES,
        verbose_name=_("category"),
        help_text=_("Alumni: Events organised for alumni, "
                    "Education: Education focused events, "
                    "Career: Career focused events, "
                    "Leisure: borrels, parties, game activities etc., "
                    "Association Affairs: general meetings or "
                    "any other board related events, "
                    "Other: anything else."),
    )

    registration_start = models.DateTimeField(
        _("registration start"),
        null=True,
        blank=True,
        help_text=_("If you set a registration period registration will be "
                    "required. If you don't set one, registration won't be "
                    "required. Prefer times when people don't have lectures, "
                    "e.g. 12:30 instead of 13:37."),
    )

    registration_end = models.DateTimeField(
        _("registration end"),
        null=True,
        blank=True,
        help_text=_("If you set a registration period registration will be "
                    "required. If you don't set one, registration won't be "
                    "required."),
    )

    cancel_deadline = models.DateTimeField(_("cancel deadline"),
                                           null=True,
                                           blank=True)

    send_cancel_email = models.BooleanField(
        _("send cancellation notifications"),
        default=True,
        help_text=_("Send an email to the organising party when a member "
                    "cancels their registration after the deadline."),
    )

    location = MultilingualField(
        models.CharField,
        _("location"),
        max_length=255,
    )

    map_location = models.CharField(
        _("location for minimap"),
        max_length=255,
        help_text=_("Location of Huygens: Heyendaalseweg 135, Nijmegen. "
                    "Location of Mercator 1: Toernooiveld 212, Nijmegen. "
                    "Not shown as text!!"),
    )

    price = models.DecimalField(
        _("price"),
        max_digits=5,
        decimal_places=2,
        default=0,
        validators=[validators.MinValueValidator(0)],
    )

    fine = models.DecimalField(
        _("fine"),
        max_digits=5,
        decimal_places=2,
        default=0,
        # Minimum fine is checked in this model's clean(), as it is only for
        # events that require registration.
        help_text=_("Fine if participant does not show up (at least €5)."),
        validators=[validators.MinValueValidator(0)],
    )

    max_participants = models.PositiveSmallIntegerField(
        _("maximum number of participants"),
        blank=True,
        null=True,
    )

    no_registration_message = MultilingualField(
        models.CharField,
        _("message when there is no registration"),
        max_length=200,
        blank=True,
        null=True,
        help_text=(format_lazy("{} {}", _("Default:"),
                               DEFAULT_NO_REGISTRATION_MESSAGE)),
    )

    published = models.BooleanField(_("published"), default=False)

    registration_reminder = models.ForeignKey(
        ScheduledMessage,
        on_delete=models.deletion.SET_NULL,
        related_name="registration_event",
        blank=True,
        null=True,
    )
    start_reminder = models.ForeignKey(
        ScheduledMessage,
        on_delete=models.deletion.SET_NULL,
        related_name="start_event",
        blank=True,
        null=True,
    )

    documents = models.ManyToManyField(
        "documents.Document",
        verbose_name=_("documents"),
        blank=True,
    )

    slide = models.ForeignKey(
        Slide,
        verbose_name="slide",
        help_text=_("Change the header-image on the event's info-page to one "
                    "specific to this event."),
        blank=True,
        on_delete=models.deletion.SET_NULL,
        null=True,
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._price = self.price
        self._registration_start = self.registration_start

    @property
    def after_cancel_deadline(self):
        return self.cancel_deadline and self.cancel_deadline <= timezone.now()

    @property
    def registration_started(self):
        return self.registration_start <= timezone.now()

    @property
    def registration_required(self):
        return bool(self.registration_start) or bool(self.registration_end)

    def has_fields(self):
        return self.registrationinformationfield_set.count() > 0

    def reached_participants_limit(self):
        """Is this event up to capacity?"""
        return (self.max_participants is not None and self.max_participants <=
                self.eventregistration_set.filter(date_cancelled=None).count())

    @property
    def registrations(self):
        """Queryset with all non-cancelled registrations"""
        return self.eventregistration_set.filter(date_cancelled=None)

    @property
    def participants(self):
        """Return the active participants"""
        if self.max_participants is not None:
            return self.registrations.order_by("date")[:self.max_participants]
        return self.registrations.order_by("date")

    @property
    def queue(self):
        """Return the waiting queue"""
        if self.max_participants is not None:
            return self.registrations.order_by("date")[self.max_participants:]
        return []

    @property
    def cancellations(self):
        """Return a queryset with the cancelled events"""
        return self.eventregistration_set.exclude(
            date_cancelled=None).order_by("date_cancelled")

    @property
    def registration_allowed(self):
        now = timezone.now()
        return (bool(self.registration_start or self.registration_end)
                and self.registration_end > now >= self.registration_start)

    @property
    def cancellation_allowed(self):
        now = timezone.now()
        return (bool(self.registration_start or self.registration_end)
                and self.registration_start <= now < self.start)

    def is_pizza_event(self):
        try:
            self.pizzaevent
            return True
        except ObjectDoesNotExist:
            return False

    def clean(self):
        super().clean()
        errors = {}
        if self.start is None:
            errors.update(
                {"start": _("Start cannot have an empty date or time field")})
        if self.end is None:
            errors.update(
                {"end": _("End cannot have an empty date or time field")})
        if self.start is not None and self.end is not None:
            if self.end < self.start:
                errors.update(
                    {"end": _("Can't have an event travel back in time")})
            if self.registration_required:
                if self.fine < 5:
                    errors.update({
                        "fine":
                        _("The fine for this event is too low "
                          "(must be at least €5).")
                    })
                for lang in settings.LANGUAGES:
                    field = "no_registration_message_" + lang[0]
                    if getattr(self, field):
                        errors.update({
                            field:
                            _("Doesn't make sense to have this "
                              "if you require registrations.")
                        })
                if not self.registration_start:
                    errors.update({
                        "registration_start":
                        _("If registration is required, you need a start of "
                          "registration")
                    })
                if not self.registration_end:
                    errors.update({
                        "registration_end":
                        _("If registration is required, you need an end of "
                          "registration")
                    })
                if not self.cancel_deadline:
                    errors.update({
                        "cancel_deadline":
                        _("If registration is required, "
                          "you need a deadline for the cancellation")
                    })
                elif self.cancel_deadline > self.start:
                    errors.update({
                        "cancel_deadline":
                        _("The cancel deadline should be"
                          " before the start of the event.")
                    })
                if (self.registration_start and self.registration_end and
                    (self.registration_start >= self.registration_end)):
                    message = _("Registration start should be before "
                                "registration end")
                    errors.update({
                        "registration_start": message,
                        "registration_end": message
                    })

        try:
            if (self.organiser is not None and self.send_cancel_email
                    and self.organiser.contact_mailinglist is None):
                errors.update({
                    "send_cancel_email":
                    _("This organiser does not "
                      "have a contact mailinglist.")
                })
        except ObjectDoesNotExist:
            pass

        if self.published:
            if (self.price != self._price and self._registration_start
                    and self._registration_start <= timezone.now()):
                errors.update({
                    "price":
                    _("You cannot change this field after "
                      "the registration has started.")
                })
            if (self._registration_start
                    and self.registration_start != self._registration_start
                    and self._registration_start <= timezone.now()):
                errors.update({
                    "registration_start":
                    _("You cannot change this field after "
                      "the registration has started.")
                })

        if errors:
            raise ValidationError(errors)

    def get_absolute_url(self):
        return reverse("events:event", args=[str(self.pk)])

    def save(self, *args, **kwargs):
        delete_collector = Collector(
            using=router.db_for_write(self.__class__, instance=self))

        if not self.pk:
            super().save(*args, **kwargs)

        if self.published:
            if self.registration_required:
                registration_reminder_time = (self.registration_start -
                                              timezone.timedelta(hours=1))
                registration_reminder = ScheduledMessage()
                if (self.registration_reminder is not None
                        and not self.registration_reminder.sent):
                    registration_reminder = self.registration_reminder

                if registration_reminder_time > timezone.now():
                    registration_reminder.title_en = "Event registration"
                    registration_reminder.title_nl = "Evenement registratie"
                    registration_reminder.body_en = ("Registration for '{}' "
                                                     "starts in 1 hour".format(
                                                         self.title_en))
                    registration_reminder.body_nl = ("Registratie voor '{}' "
                                                     "start in 1 uur".format(
                                                         self.title_nl))
                    registration_reminder.category = Category.objects.get(
                        key=Category.EVENT)
                    registration_reminder.time = registration_reminder_time
                    registration_reminder.url = (
                        f"{settings.BASE_URL}"
                        f'{reverse("events:event", args=[self.id])}')

                    registration_reminder.save()
                    self.registration_reminder = registration_reminder
                    self.registration_reminder.users.set(
                        Member.current_members.all())
                elif registration_reminder.pk is not None:
                    delete_collector.add([self.registration_reminder])
                    self.registration_reminder = None

            start_reminder_time = self.start - timezone.timedelta(hours=1)
            start_reminder = ScheduledMessage()
            if self.start_reminder is not None and not self.start_reminder.sent:
                start_reminder = self.start_reminder

            if start_reminder_time > timezone.now():
                start_reminder.title_en = "Event"
                start_reminder.title_nl = "Evenement"
                start_reminder.body_en = f"'{self.title_en}' starts in " "1 hour"
                start_reminder.body_nl = f"'{self.title_nl}' begint over " "1 uur"
                start_reminder.category = Category.objects.get(
                    key=Category.EVENT)
                start_reminder.time = start_reminder_time
                start_reminder.save()
                self.start_reminder = start_reminder
                if self.registration_required:
                    self.start_reminder.users.set(
                        [r.member for r in self.participants if r.member])
                else:
                    self.start_reminder.users.set(Member.current_members.all())
            elif start_reminder.pk is not None:
                delete_collector.add([self.start_reminder])
                self.start_reminder = None
        else:
            if (self.registration_reminder is not None
                    and not self.registration_reminder.sent):
                delete_collector.add([self.registration_reminder])
                self.registration_reminder = None

            if self.start_reminder is not None and not self.start_reminder.sent:
                delete_collector.add([self.start_reminder])
                self.start_reminder = None

        super().save()
        delete_collector.delete()

    def delete(self, using=None, keep_parents=False):
        using = using or router.db_for_write(self.__class__, instance=self)
        collector = Collector(using=using)
        collector.collect([self], keep_parents=keep_parents)

        if (self.registration_reminder is not None
                and not self.registration_reminder.sent):
            collector.add([self.registration_reminder])
        if self.start_reminder is not None and not self.start_reminder.sent:
            collector.add([self.start_reminder])
        if self.is_pizza_event():
            collector.add([self.pizzaevent])

        return collector.delete()

    def __str__(self):
        return "{}: {}".format(
            self.title,
            timezone.localtime(self.start).strftime("%Y-%m-%d %H:%M"))

    class Meta:
        ordering = ("-start", )
        permissions = (("override_organiser",
                        "Can access events as if organizing"), )
Exemple #25
0
class Slide(models.Model, metaclass=ModelTranslateMeta):
    """Describes an announcement"""

    title = CharField(
        verbose_name=_("Title"),
        help_text=_("The title of the slide; just for the admin."),
        blank=False,
        max_length=100,
    )

    content = MultilingualField(
        ImageField,
        verbose_name=_("Content"),
        help_text=_("The content of the slide; what image to display."),
        blank=False,
        upload_to="public/announcements/slides/",
    )

    since = models.DateTimeField(
        verbose_name=_("Display since"),
        help_text=_(
            "Hide this slide before this time. When all date- and "
            "time-fields are left blank, the slide won't "
            "be visible. It will, however, be visible on an event-page "
            "if it's linked to an event."),
        default=timezone.now,
        blank=True,
        null=True,
    )

    until = models.DateTimeField(
        verbose_name=_("Display until"),
        help_text=_("Hide this slide after this time."),
        blank=True,
        null=True,
    )

    order = models.PositiveIntegerField(
        verbose_name=_("Order"),
        help_text=_("Approximately where this slide "
                    "should appear in the order"),
        default=0,
    )

    members_only = models.BooleanField(
        verbose_name=_("Display only for authenticated members"),
        default=False)

    url = models.URLField(
        verbose_name=_("Link"),
        help_text=_("Place the user is taken to when clicking the slide"),
        blank=True,
        null=True,
    )

    url_blank = models.BooleanField(
        verbose_name=_("Link outside thalia.nu"),
        help_text=_("Clicking the slide will open a new tab"),
        default=False,
    )

    class Meta:
        ordering = ("-since", )

    @property
    def is_visible(self):
        """Is this slide currently visible"""
        return ((self.until is None or self.until > timezone.now())
                and (self.since is None or self.since <= timezone.now())
                and not (self.since is None and self.until is None))

    def __str__(self):
        return self.title