Ejemplo n.º 1
0
class Membership(ExportModelOperationsMixin("membership"), TimeStampedModel):
    """
    Model that represents the membership of a person in a support group

    This model also indicates if the person is referent for this support group
    """

    MEMBERSHIP_TYPE_MEMBER = 10
    MEMBERSHIP_TYPE_MANAGER = 50
    MEMBERSHIP_TYPE_REFERENT = 100
    MEMBERSHIP_TYPE_CHOICES = (
        (MEMBERSHIP_TYPE_MEMBER, "Membre du groupe"),
        (MEMBERSHIP_TYPE_MANAGER, "Membre gestionnaire"),
        (MEMBERSHIP_TYPE_REFERENT, "Animateur⋅rice"),
    )

    objects = MembershipQuerySet.as_manager()

    person = models.ForeignKey(
        "people.Person",
        related_name="memberships",
        on_delete=models.CASCADE,
        editable=False,
    )

    supportgroup = models.ForeignKey(
        "SupportGroup",
        related_name="memberships",
        on_delete=models.CASCADE,
        editable=False,
    )

    membership_type = models.IntegerField(
        _("Statut dans le groupe"),
        choices=MEMBERSHIP_TYPE_CHOICES,
        default=MEMBERSHIP_TYPE_MEMBER,
    )

    notifications_enabled = models.BooleanField(
        _("Recevoir les notifications de ce groupe"),
        default=True,
        help_text=_(
            "Je recevrai des messages en cas de modification du groupe."),
    )

    class Meta:
        verbose_name = _("adhésion")
        verbose_name_plural = _("adhésions")
        unique_together = ("supportgroup", "person")

    def __str__(self):
        return _("{person} --> {supportgroup},  ({type})").format(
            person=self.person,
            supportgroup=self.supportgroup,
            type=self.get_membership_type_display(),
        )

    @property
    def is_referent(self):
        return self.membership_type >= Membership.MEMBERSHIP_TYPE_REFERENT

    @property
    def is_manager(self):
        return self.membership_type >= Membership.MEMBERSHIP_TYPE_MANAGER
Ejemplo n.º 2
0
class PersonEmail(ExportModelOperationsMixin("person_email"), models.Model):
    """
    Model that represent a person email address
    """

    objects = PersonEmailManager()

    address = models.EmailField(
        _("adresse email"),
        blank=False,
        help_text=_(
            "L'adresse email de la personne, utilisée comme identifiant"),
    )

    _bounced = models.BooleanField(
        _("email rejeté"),
        default=False,
        db_column="bounced",
        help_text=
        _("Indique que des mails envoyés ont été rejetés par le serveur distant"
          ),
    )

    bounced_date = models.DateTimeField(
        _("date de rejet de l'email"),
        null=True,
        blank=True,
        help_text=_(
            "Si des mails ont été rejetés, indique la date du dernier rejet"),
    )

    @property
    def bounced(self):
        return self._bounced

    @bounced.setter
    def bounced(self, value):
        if value and self._bounced is False:
            self.bounced_date = timezone.now()
        self._bounced = value

    person = models.ForeignKey(Person,
                               on_delete=models.CASCADE,
                               null=False,
                               related_name="emails")

    class Meta:
        order_with_respect_to = "person"
        verbose_name = _("Email")

    def __str__(self):
        return self.address

    def validate_unique(self, exclude=None):
        errors = {}
        try:
            super().validate_unique(exclude=exclude)
        except ValidationError as e:
            errors = e.message_dict

        if exclude is None or "address" not in exclude:
            qs = PersonEmail.objects.filter(address__iexact=self.address)
            if not self._state.adding and self.pk:
                qs = qs.exclude(pk=self.pk)

            if qs.exists():
                errors.setdefault("address", []).append(
                    ValidationError(
                        message=_("Cette adresse email est déjà utilisée."),
                        code="unique",
                    ))

        if errors:
            raise ValidationError(errors)

    def clean(self):
        self.address = PersonEmail.objects.normalize_email(self.address)
Ejemplo n.º 3
0
class ProloginUser(ExportModelOperationsMixin('user'), AbstractUser,
                   AddressableModel):
    @staticmethod
    def upload_seed(instance):
        return 'prologinuser/{}'.format(instance.pk).encode()

    def upload_avatar_to(self, *args, **kwargs):
        return upload_path('avatar',
                           using=ProloginUser.upload_seed)(self, *args,
                                                           **kwargs)

    def upload_picture_to(self, *args, **kwargs):
        return upload_path('picture',
                           using=ProloginUser.upload_seed)(self, *args,
                                                           **kwargs)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    gender = GenderField(blank=True, null=True, db_index=True)
    school_stage = EnumField(EducationStage,
                             null=True,
                             db_index=True,
                             blank=True,
                             verbose_name=_("Educational stage"))
    phone = models.CharField(max_length=16,
                             blank=True,
                             verbose_name=_("Phone"))
    birthday = models.DateField(blank=True,
                                null=True,
                                verbose_name=_("Birth day"))
    allow_mailing = models.BooleanField(
        default=True,
        blank=True,
        db_index=True,
        verbose_name=_("Allow Prologin to send me emails"),
        help_text=_("We only mail you to provide useful information "
                    "during the various stages of the contest. "
                    "We hate spam as much as you do!"))
    signature = models.TextField(blank=True, verbose_name=_("Signature"))
    preferred_language = CodingLanguageField(
        blank=True, db_index=True, verbose_name=_("Preferred coding language"))
    timezone = TimeZoneField(default=settings.TIME_ZONE,
                             verbose_name=_("Time zone"))
    preferred_locale = models.CharField(max_length=8,
                                        blank=True,
                                        verbose_name=_("Locale"),
                                        choices=settings.LANGUAGES)

    avatar = ResizeOnSaveImageField(upload_to=upload_avatar_to,
                                    storage=overwrite_storage,
                                    fit_into=settings.PROLOGIN_MAX_AVATAR_SIZE,
                                    blank=True,
                                    verbose_name=_("Profile picture"))
    picture = ResizeOnSaveImageField(
        upload_to=upload_picture_to,
        storage=overwrite_storage,
        fit_into=settings.PROLOGIN_MAX_AVATAR_SIZE,
        blank=True,
        verbose_name=_("Official member picture"))

    # MD5 password from <2015 Drupal website
    legacy_md5_password = models.CharField(max_length=32, blank=True)

    objects = ProloginUserManager()

    def get_homes(self):
        return [
            c for c in self.contestants.order_by('-edition__year')
            if c.has_home
        ]

    def get_contestants(self):
        return self.contestants.select_related('edition').order_by(
            '-edition__year')

    def get_involved_contestants(self):
        return self.get_contestants().exclude(
            assignation_semifinal=Assignation.not_assigned.value)

    def can_edit_profile(self, edition):
        if edition is None:
            # no edition, fallback to allow
            return True
        if self.has_perm('users.edit-during-contest'):
            # privileged
            return True
        event, type = edition.phase
        if event is None:
            # future or finished, allow
            return True
        assigned_semifinal = self.contestants.filter(
            edition=edition,
            assignation_semifinal=Assignation.assigned.value).exists()
        if event == Event.Type.qualification and type == 'corrected' and assigned_semifinal:
            return False
        if not assigned_semifinal:
            return True
        # below: assigned to semifinal
        assigned_final = self.contestants.filter(
            edition=edition,
            assignation_final=Assignation.assigned.value).exists()
        if event == Event.Type.semifinal:
            if type in ('active', 'done'):
                return False
            if type == 'corrected' and assigned_final:
                return False
        if not assigned_final:
            return True
        # below: assigned to final
        if event == Event.Type.final and type in ('active', 'done'):
            return False
        return True

    @property
    def preferred_language_enum(self):
        return Language[self.preferred_language]

    def plaintext_password(self, event):
        event_salt = str(event) if event else ''
        return (base64.urlsafe_b64encode(
            hashlib.sha1("{}{}{}{}".format(
                self.first_name, self.last_name, event_salt,
                settings.PLAINTEXT_PASSWORD_SALT).encode(
                    'utf-8')).digest()).decode('ascii').translate(
                        settings.PLAINTEXT_PASSWORD_DISAMBIGUATION)
                [:settings.PLAINTEXT_PASSWORD_LENGTH])

    @property
    def normalized_username(self):
        return slugify("{}{}".format(self.first_name[:1], self.last_name))

    @property
    def avatar_or_picture(self):
        if self.avatar:
            return self.avatar
        return self.picture

    @property
    def picture_or_avatar(self):
        if self.picture:
            return self.picture
        return self.avatar

    @property
    def unsubscribe_token(self):
        user_id = str(self.id).encode()
        secret = settings.SECRET_KEY.encode()
        return hashlib.sha256(user_id + secret).hexdigest()

    def has_partial_address(self):
        return any((self.address, self.city, self.country, self.postal_code))

    def has_complete_address(self):
        return all((self.address, self.city, self.country, self.postal_code))

    def has_complete_profile(self):
        return self.has_complete_address() and all((self.phone, self.birthday))

    def get_absolute_url(self):
        return reverse('users:profile', args=[self.pk])

    def get_unsubscribe_url(self):
        return '{}{}?uid={}&token={}'.format(settings.SITE_BASE_URL,
                                             reverse('users:unsubscribe'),
                                             self.id, self.unsubscribe_token)

    def young_enough_to_compete(self, edition):
        if not self.birthday:
            return False

        last_ok_year = edition - settings.PROLOGIN_MAX_AGE
        return last_ok_year <= self.birthday.year
Ejemplo n.º 4
0
class Event(
    ExportModelOperationsMixin("event"),
    BaseAPIResource,
    NationBuilderResource,
    LocationMixin,
    ImageMixin,
    DescriptionMixin,
    ContactMixin,
):
    """
    Model that represents an event
    """

    objects = EventQuerySet.as_manager()

    name = models.CharField(
        _("nom"), max_length=255, blank=False, help_text=_("Le nom de l'événement")
    )

    VISIBILITY_ADMIN = "A"
    VISIBILITY_ORGANIZER = "O"
    VISIBILITY_PUBLIC = "P"
    VISIBILITY_CHOICES = (
        (VISIBILITY_ADMIN, "Caché"),
        (VISIBILITY_ORGANIZER, "Visible par les organisateurs"),
        (VISIBILITY_PUBLIC, "Public"),
    )

    visibility = models.CharField(
        "Visibilité",
        max_length=1,
        choices=VISIBILITY_CHOICES,
        default=VISIBILITY_PUBLIC,
    )

    subtype = models.ForeignKey(
        "EventSubtype",
        verbose_name="Sous-type",
        related_name="events",
        on_delete=models.PROTECT,
        default=get_default_subtype,
    )

    nb_path = models.CharField(_("NationBuilder path"), max_length=255, blank=True)

    tags = models.ManyToManyField("EventTag", related_name="events", blank=True)

    start_time = CustomDateTimeField(_("date et heure de début"), blank=False)
    end_time = CustomDateTimeField(_("date et heure de fin"), blank=False)
    max_participants = models.IntegerField(
        "Nombre maximum de participants", blank=True, null=True
    )
    allow_guests = models.BooleanField(
        "Autoriser les participant⋅e⋅s à inscrire des invité⋅e⋅s", default=False
    )
    facebook = FacebookEventField("Événement correspondant sur Facebook", blank=True)

    attendees = models.ManyToManyField(
        "people.Person", related_name="events", through="RSVP"
    )

    organizers = models.ManyToManyField(
        "people.Person", related_name="organized_events", through="OrganizerConfig"
    )
    organizers_groups = models.ManyToManyField(
        "groups.SupportGroup",
        related_name="organized_events",
        through="OrganizerConfig",
    )

    report_image = StdImageField(
        verbose_name=_("image de couverture"),
        blank=True,
        variations={"thumbnail": (400, 250), "banner": (1200, 400)},
        upload_to=report_image_path,
        help_text=_(
            "Cette image apparaîtra en tête de votre compte-rendu, et dans les partages que vous ferez du"
            " compte-rendu sur les réseaux sociaux."
        ),
    )

    report_content = DescriptionField(
        verbose_name=_("compte-rendu de l'événement"),
        blank=True,
        allowed_tags="allowed_tags",
        help_text=_(
            "Ajoutez un compte-rendu de votre événement. N'hésitez pas à inclure des photos."
        ),
    )

    report_summary_sent = models.BooleanField(
        "Le mail de compte-rendu a été envoyé", default=False
    )

    subscription_form = models.OneToOneField(
        "people.PersonForm", null=True, blank=True, on_delete=models.PROTECT
    )
    payment_parameters = JSONField(
        verbose_name=_("Paramètres de paiement"), null=True, blank=True
    )

    scanner_event = models.IntegerField(
        "L'ID de l'événement sur le logiciel de tickets", blank=True, null=True
    )
    scanner_category = models.IntegerField(
        "La catégorie que doivent avoir les tickets sur scanner", blank=True, null=True
    )

    enable_jitsi = models.BooleanField("Activer la visio-conférence", default=False)

    participation_template = models.TextField(
        _("Template pour la page de participation"), blank=True, null=True
    )

    do_not_list = models.BooleanField(
        "Ne pas lister l'événement",
        default=False,
        help_text="L'événement n'apparaîtra pas sur la carte, ni sur le calendrier "
        "et ne sera pas cherchable via la recherche interne ou les moteurs de recherche.",
    )

    legal = JSONField(
        _("Informations juridiques"),
        default=dict,
        blank=True,
        encoder=CustomJSONEncoder,
    )

    class Meta:
        verbose_name = _("événement")
        verbose_name_plural = _("événements")
        ordering = ("-start_time", "-end_time")
        permissions = (
            # DEPRECIATED: every_event was set up as a potential solution to Rest Framework django permissions
            # Permission class default behaviour of requiring both global permissions and object permissions before
            # allowing users. Was not used in the end.s
            ("every_event", _("Peut éditer tous les événements")),
            ("view_hidden_event", _("Peut voir les événements non publiés")),
        )
        indexes = (
            models.Index(
                fields=["start_time", "end_time"], name="events_datetime_index"
            ),
            models.Index(fields=["end_time"], name="events_end_time_index"),
            models.Index(fields=["nb_path"], name="events_nb_path_index"),
        )

    def __str__(self):
        return f"{self.name} ({self.get_display_date()})"

    def __repr__(self):
        return f"{self.__class__.__name__}(id={str(self.pk)!r}, name={self.name!r})"

    def to_ics(self):
        event_url = front_url("view_event", args=[self.pk], auto_login=False)
        return ics.Event(
            name=self.name,
            begin=self.start_time,
            end=self.end_time,
            uid=str(self.pk),
            description=self.description + f"<p>{event_url}</p>",
            location=self.short_address,
            url=event_url,
        )

    @property
    def participants(self):
        try:
            return self.all_attendee_count
        except AttributeError:
            if self.subscription_form:
                return (
                    self.rsvps.annotate(
                        identified_guests_count=Count("identified_guests")
                    ).aggregate(participants=Sum(F("identified_guests_count") + 1))[
                        "participants"
                    ]
                    or 0
                )
            return (
                self.rsvps.aggregate(participants=Sum(models.F("guests") + 1))[
                    "participants"
                ]
                or 0
            )

    @property
    def type(self):
        return self.subtype.type

    def get_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)
        end_time = self.end_time.astimezone(tz)

        if start_time.date() == end_time.date():
            date = formats.date_format(start_time, "DATE_FORMAT")
            return _("le {date}, de {start_hour} à {end_hour}").format(
                date=date,
                start_hour=formats.time_format(start_time, "TIME_FORMAT"),
                end_hour=formats.time_format(end_time, "TIME_FORMAT"),
            )

        return _("du {start_date}, {start_time} au {end_date}, {end_time}").format(
            start_date=formats.date_format(start_time, "DATE_FORMAT"),
            start_time=formats.date_format(start_time, "TIME_FORMAT"),
            end_date=formats.date_format(end_time, "DATE_FORMAT"),
            end_time=formats.date_format(end_time, "TIME_FORMAT"),
        )

    def get_simple_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)

        return _("le {date} à {time}").format(
            date=formats.date_format(start_time, "DATE_FORMAT"),
            time=formats.time_format(start_time, "TIME_FORMAT"),
        )

    def is_past(self):
        return timezone.now() > self.end_time

    def is_current(self):
        return self.start_time < timezone.now() < self.end_time

    def clean(self):
        if self.start_time and self.end_time and self.end_time < self.start_time:
            raise ValidationError(
                {
                    "end_time": _(
                        "La date de fin de l'événement doit être postérieure à sa date de début."
                    )
                }
            )

    def get_price_display(self):
        if self.payment_parameters is None:
            return None

        base_price = self.payment_parameters.get("price", 0)
        min_price = base_price
        max_price = base_price

        for mapping in self.payment_parameters.get("mappings", []):
            prices = [m["price"] for m in mapping["mapping"]]
            min_price += min(prices)
            max_price += max(prices)

        if min_price == max_price == 0:
            if "free_pricing" in self.payment_parameters:
                return "Prix libre"
            else:
                return None

        if min_price == max_price:
            display = "{} €".format(floatformat(min_price / 100, 2))
        else:
            display = "de {} à {} €".format(
                floatformat(min_price / 100, 2), floatformat(max_price / 100, 2)
            )

        if "free_pricing" in self.payment_parameters:
            display += " + montant libre"

        return display

    @property
    def is_free(self):
        return self.payment_parameters is None

    def get_price(self, submission_data: dict = None):
        price = self.payment_parameters.get("price", 0)

        if submission_data is None:
            return price

        for mapping in self.payment_parameters.get("mappings", []):
            values = [submission_data.get(field) for field in mapping["fields"]]

            d = {tuple(v for v in m["values"]): m["price"] for m in mapping["mapping"]}

            price += d[tuple(values)]

        if "free_pricing" in self.payment_parameters:
            field = self.payment_parameters["free_pricing"]
            price += max(0, int(submission_data.get(field, 0) * 100))

        return price

    def get_absolute_url(self):
        return front_url("view_event", args=[self.pk])
Ejemplo n.º 5
0
class Group(ExportModelOperationsMixin('group'), EffigiaModel):
    cover_image = models.ImageField(upload_to='covers/group/')
Ejemplo n.º 6
0
class RRset(ExportModelOperationsMixin('RRset'), models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    touched = models.DateTimeField(auto_now=True)
    domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
    subname = models.CharField(
        max_length=178,
        blank=True,
        validators=[
            validate_lower,
            RegexValidator(
                regex=r'^([*]|(([*][.])?([a-z0-9_-]+[.])*[a-z0-9_-]+))$',
                message=
                'Subname can only use (lowercase) a-z, 0-9, ., -, and _, '
                'may start with a \'*.\', or just be \'*\'.',
                code='invalid_subname')
        ])
    type = models.CharField(
        max_length=10,
        validators=[
            validate_upper,
            RegexValidator(
                regex=r'^[A-Z][A-Z0-9]*$',
                message=
                'Type must be uppercase alphanumeric and start with a letter.',
                code='invalid_type')
        ])
    ttl = models.PositiveIntegerField()

    objects = RRsetManager()

    class Meta:
        constraints = [
            ExclusionConstraint(
                name='cname_exclusivity',
                expressions=[
                    ('domain', RangeOperators.EQUAL),
                    ('subname', RangeOperators.EQUAL),
                    (RawSQL("int4(type = 'CNAME')",
                            ()), RangeOperators.NOT_EQUAL),
                ],
            ),
        ]
        unique_together = (("domain", "subname", "type"), )

    @staticmethod
    def construct_name(subname, domain_name):
        return '.'.join(filter(None, [subname, domain_name])) + '.'

    @property
    def name(self):
        return self.construct_name(self.subname, self.domain.name)

    def save(self, *args, **kwargs):
        self.full_clean(validate_unique=False)
        super().save(*args, **kwargs)

    def clean_records(self, records_presentation_format):
        """
        Validates the records belonging to this set. Validation rules follow the DNS specification; some types may
        incur additional validation rules.

        Raises ValidationError if violation of DNS specification is found.

        Returns a set of records in canonical presentation format.

        :param records_presentation_format: iterable of records in presentation format
        """
        rdtype = rdatatype.from_text(self.type)
        errors = []

        if self.type == 'CNAME':
            if self.subname == '':
                errors.append('CNAME RRset cannot have empty subname.')
            if len(records_presentation_format) > 1:
                errors.append(
                    'RRset of type CNAME cannot have multiple records.')

        def _error_msg(record, detail):
            return f'Record content of {self.type} {self.name} invalid: \'{record}\': {detail}'

        records_canonical_format = set()
        for r in records_presentation_format:
            try:
                r_canonical_format = RR.canonical_presentation_format(
                    r, rdtype)
            except binascii.Error:
                # e.g., odd-length string
                errors.append(
                    _error_msg(
                        r,
                        'Cannot parse hexadecimal or base64 record contents'))
            except dns.exception.SyntaxError as e:
                # e.g., A/127.0.0.999
                if 'quote' in e.args[0]:
                    errors.append(
                        _error_msg(
                            r,
                            f'Data for {self.type} records must be given using quotation marks.'
                        ))
                else:
                    errors.append(
                        _error_msg(
                            r,
                            f'Record content malformed: {",".join(e.args)}'))
            except dns.name.NeedAbsoluteNameOrOrigin:
                errors.append(
                    _error_msg(
                        r,
                        'Hostname must be fully qualified (i.e., end in a dot: "example.com.")'
                    ))
            except ValueError:
                # e.g., string ("asdf") cannot be parsed into int on base 10
                errors.append(_error_msg(r, 'Cannot parse record contents'))
            except Exception as e:
                # TODO see what exceptions raise here for faulty input
                raise e
            else:
                if r_canonical_format in records_canonical_format:
                    errors.append(
                        _error_msg(
                            r,
                            f'Duplicate record content: this is identical to '
                            f'\'{r_canonical_format}\''))
                else:
                    records_canonical_format.add(r_canonical_format)

        if any(errors):
            raise ValidationError(errors)

        return records_canonical_format

    def save_records(self, records):
        """
        Updates this RR set's resource records, discarding any old values.

        Records are expected in presentation format and are converted to canonical
        presentation format (e.g., 127.00.0.1 will be converted to 127.0.0.1).
        Raises if a invalid set of records is provided.

        This method triggers the following database queries:
        - one DELETE query
        - one SELECT query for comparison of old with new records
        - one INSERT query, if one or more records were added

        Changes are saved to the database immediately.

        :param records: list of records in presentation format
        """
        new_records = self.clean_records(records)

        # Delete RRs that are not in the new record list from the DB
        self.records.exclude(content__in=new_records).delete()  # one DELETE

        # Retrieve all remaining RRs from the DB
        unchanged_records = set(r.content
                                for r in self.records.all())  # one SELECT

        # Save missing RRs from the new record list to the DB
        added_records = new_records - unchanged_records
        rrs = [RR(rrset=self, content=content) for content in added_records]
        RR.objects.bulk_create(rrs)  # One INSERT

    def __str__(self):
        return '<RRSet %s domain=%s type=%s subname=%s>' % (
            self.pk, self.domain.name, self.type, self.subname)
Ejemplo n.º 7
0
class Lawn(ExportModelOperationsMixin("lawn"), Model):
    location = CharField(max_length=100)
Ejemplo n.º 8
0
class ResourceData(ExportModelOperationsMixin("resource_data"),
                   models.Model):  # type: ignore # noqa E501
    class Tier(models.IntegerChoices):
        TIER_1 = 0, _("T1 - Placid")
        TIER_2 = 1, _("T2 - Temperate")
        TIER_3 = 2, _("T3 - Rugged")
        TIER_4 = 3, _("T4 - Inhospitable")
        TIER_5 = 4, _("T5 - Turbulent")
        TIER_6 = 5, _("T6 - Fierce")
        TIER_7 = 6, _("T7 - Savage")
        TIER_8 = 7, _("T8 - Brutal")

    item = models.OneToOneField(Item,
                                on_delete=models.CASCADE,
                                related_name="resource_data")

    is_embedded = models.BooleanField()
    exo_only = models.BooleanField(default=False)

    max_tier = models.PositiveSmallIntegerField(
        _("Max Tier"),
        choices=Tier.choices,
        help_text=_("Max tier of world to be found on. Starts at 0."),
    )

    min_tier = models.PositiveSmallIntegerField(
        _("Min Tier"),
        choices=Tier.choices,
        help_text=_("Min tier of world to be found on. Starts at 0."),
    )

    best_max_tier = models.PositiveSmallIntegerField(
        _("Max Tier"),
        choices=Tier.choices,
        help_text=_("Max tier of world to be found on. Starts at 0."),
    )

    best_min_tier = models.PositiveSmallIntegerField(
        _("Min Tier"),
        choices=Tier.choices,
        help_text=_("Min tier of world to be found on. Starts at 0."),
    )

    shape = models.PositiveSmallIntegerField()
    size_max = models.PositiveSmallIntegerField()
    size_min = models.PositiveSmallIntegerField()
    altitude_max = models.PositiveSmallIntegerField()
    altitude_min = models.PositiveSmallIntegerField()
    distance_max = models.PositiveSmallIntegerField(blank=True, null=True)
    distance_min = models.PositiveSmallIntegerField(blank=True, null=True)
    cave_weighting = models.FloatField()
    size_skew_to_min = models.FloatField()
    blocks_above_max = models.PositiveSmallIntegerField()
    blocks_above_min = models.PositiveSmallIntegerField()
    liquid_above_max = models.PositiveSmallIntegerField()
    liquid_above_min = models.PositiveSmallIntegerField()
    noise_frequency = models.FloatField(blank=True, null=True)
    noise_threshold = models.FloatField(blank=True, null=True)
    liquid_favorite = models.ForeignKey(Item,
                                        on_delete=models.CASCADE,
                                        blank=True,
                                        null=True,
                                        related_name="+")
    three_d_weighting = models.FloatField()
    surface_favorite = models.ForeignKey(Item,
                                         on_delete=models.CASCADE,
                                         blank=True,
                                         null=True,
                                         related_name="+")
    surface_weighting = models.FloatField()
    altitude_best_lower = models.PositiveSmallIntegerField()
    altitude_best_upper = models.PositiveSmallIntegerField()
    distance_best_lower = models.PositiveSmallIntegerField(blank=True,
                                                           null=True)
    distance_best_upper = models.PositiveSmallIntegerField(blank=True,
                                                           null=True)
    blocks_above_best_lower = models.PositiveSmallIntegerField()
    blocks_above_best_upper = models.PositiveSmallIntegerField()
    liquid_above_best_upper = models.PositiveSmallIntegerField()
    liquid_above_best_lower = models.PositiveSmallIntegerField()
    liquid_second_favorite = models.ForeignKey(Item,
                                               on_delete=models.CASCADE,
                                               blank=True,
                                               null=True,
                                               related_name="+")
    surface_second_favorite = models.ForeignKey(Item,
                                                on_delete=models.CASCADE,
                                                blank=True,
                                                null=True,
                                                related_name="+")

    @property
    def best_world_types(self):
        types = []
        for world_type in self.best_worlds.all():
            types.append(world_type.world_type)

        return types
Ejemplo n.º 9
0
class RecipeRequirement(ExportModelOperationsMixin("recipe_requirement"),
                        models.Model):  # type: ignore # noqa E501
    skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
    level = models.PositiveSmallIntegerField()
Ejemplo n.º 10
0
class Metal(ExportModelOperationsMixin("metal"),
            GameObj):  # type: ignore # noqa E501
    pass
Ejemplo n.º 11
0
class Item(ExportModelOperationsMixin("item"),
           GameObj):  # type: ignore # noqa E501
    item_subtitle = models.ForeignKey(Subtitle,
                                      on_delete=models.SET_NULL,
                                      blank=True,
                                      null=True)
    string_id = models.CharField(_("String ID"), max_length=64, db_index=True)
    name = models.CharField(_("Name"), max_length=64)
    mint_value = models.FloatField(_("Chrysominter Value"),
                                   null=True,
                                   blank=True)
    max_stack = models.PositiveSmallIntegerField(default=100)
    can_be_sold = models.BooleanField(db_index=True, default=True)
    list_type = models.ForeignKey(
        LocalizedString,
        on_delete=models.CASCADE,
        related_name="+",
        blank=True,
        null=True,
    )
    description = models.ForeignKey(
        LocalizedString,
        on_delete=models.CASCADE,
        related_name="+",
        blank=True,
        null=True,
    )
    is_resource = models.BooleanField(default=False, db_index=True)
    prestige = models.PositiveSmallIntegerField(default=0)
    mine_xp = models.PositiveSmallIntegerField(default=0)
    build_xp = models.PositiveSmallIntegerField(default=0)
    is_block = models.BooleanField(default=False, db_index=True)
    is_liquid = models.BooleanField(default=False, db_index=True)
    default_color = models.ForeignKey(Color,
                                      on_delete=models.CASCADE,
                                      blank=True,
                                      null=True)

    image = models.ImageField(storage=select_storage("items"),
                              blank=True,
                              null=True)
    image_small = models.ImageField(storage=select_storage("items"),
                                    blank=True,
                                    null=True)

    class Meta:
        indexes = [
            GinIndex(fields=["string_id"]),
        ]

    @property
    def default_name(self):  # pylint: disable=invalid-overridden-method
        return self.string_id

    @property
    def english(self):
        return super().default_name

    @property
    def buy_locations(self):
        return self.itemshopstandprice_set.filter(active=True)

    @property
    def sell_locations(self):
        return self.itemrequestbasketprice_set.filter(active=True)

    @property
    def has_colors(self):
        return self.game_id in get_block_color_item_ids()

    @property
    def has_world_colors(self):
        return self.game_id in get_world_block_color_item_ids()

    @property
    def has_metal_variants(self):
        return self.game_id in get_block_metal_item_ids()

    @property
    def next_shop_stand_update(self):
        return get_next_rank_update(self.itemsellrank_set.all())

    @property
    def next_request_basket_update(self):
        return get_next_rank_update(self.itembuyrank_set.all())
Ejemplo n.º 12
0
class Subtitle(ExportModelOperationsMixin("subtitle"),
               GameObj):  # type: ignore # noqa E501
    pass
Ejemplo n.º 13
0
class Picture(ExportModelOperationsMixin("picture"), models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    photo = models.ImageField(upload_to=get_photo_upload_path)
    tags = models.TextField(
        blank=True,
        validators=[
            RegexValidator(
                r"^[a-z0-9- ]*$",
                "Tags must only contain lowercase characters a-z, numbers, and dashes (-)",
                code="invalid",
            ),
            validate_tags_under_max_length,
            validate_not_too_many_tags,
        ],
    )
    uploaded_at = models.DateTimeField(default=now)
    updated_at = models.DateTimeField(default=now)
    public_id = models.UUIDField(default=uuid.uuid4,
                                 editable=False,
                                 unique=True)

    # Thumbnails
    thumbnail_w_272 = models.ImageField(upload_to="thumbnails/w272/%Y/%m/%d/",
                                        blank=True)

    # Note: be sure to update how this is done (trigger in postgres)
    # once django releases a good way to do Stored Generated Columns
    # https://stackoverflow.com/questions/59675402/django-full-text-searchvectorfield-obsolete-in-postgresql
    indexed_tags_search = SearchVectorField(null=True)

    uploaded_by = models.ForeignKey(CustomUser,
                                    models.SET_NULL,
                                    blank=True,
                                    null=True)

    class Meta:
        indexes = [GinIndex(fields=["indexed_tags_search"])]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.thumbnail_w_272:
            self.thumbnail_w_272 = self.make_thumbnail(
                self.photo, (272, self.photo.height))
        self.tags = " ".join(set(self.tags.split()))
        super().save(*args, **kwargs)

    def make_thumbnail(self, image, size):
        """Makes thumbnails of given size from given image
        
        Args:
            image (``django.ImageField``): The image to generate a thumbnail for
            size (``tuple`` of ``int``): The width and height to generate

        Returns:
            ``django.File``: The resized image
        """
        image.open()
        im = Image.open(image)
        im.thumbnail(size)  # resize image

        thumb_io = io.BytesIO()  # create a BytesIO object

        im = im.convert("RGB")
        im.save(thumb_io, "JPEG", quality=85)  # save image to BytesIO object

        thumbnail = File(thumb_io, name=pathlib.Path(
            image.name).name)  # create a django friendly File object

        return thumbnail

    # TODO: Will get called multiple times in template. Maybe persist when it is created?
    @property
    def split_tags(self):
        tags = str(self.tags).split()
        data = {"above_tags": [], "below_tags": []}

        max_width = 85  # Max width of the theoretical tag bar
        expander_length = 5  # Width of the "click to expand" symbol
        static_width_addition = 4  # How much to add in addition to each letter
        current_width = 0
        above = True
        # TODO: Minor optimization, stop adding once above is hit
        for tag in tags:
            current_width += static_width_addition + len(tag)
            if current_width + expander_length > max_width:
                above = False

            if above:
                data["above_tags"].append(tag)
            else:
                data["below_tags"].append(tag)
        return data
Ejemplo n.º 14
0
class SupportGroup(
        ExportModelOperationsMixin("support_group"),
        BaseAPIResource,
        LocationMixin,
        ImageMixin,
        DescriptionMixin,
        ContactMixin,
):
    """
    Model that represents a support group
    """

    TYPE_LOCAL_GROUP = "L"
    TYPE_THEMATIC = "B"
    TYPE_FUNCTIONAL = "F"
    TYPE_PROFESSIONAL = "P"
    TYPE_2022 = "2"

    TYPE_LFI_CHOICES = (
        (TYPE_LOCAL_GROUP, "Groupe local"),
        (TYPE_THEMATIC, "Groupe thématique"),
        (TYPE_FUNCTIONAL, "Groupe fonctionnel"),
        (TYPE_PROFESSIONAL, "Groupe professionel"),
    )
    TYPE_NSP_CHOICES = ((TYPE_2022,
                         "Équipe de soutien « Nous Sommes Pour ! »"), )

    TYPE_CHOICES = TYPE_LFI_CHOICES + TYPE_NSP_CHOICES

    TYPE_PARAMETERS = {
        TYPE_LOCAL_GROUP: {
            "color": "#4a64ac",
            "icon_name": "users"
        },
        TYPE_THEMATIC: {
            "color": "#49b37d",
            "icon_name": "book"
        },
        TYPE_FUNCTIONAL: {
            "color": "#e14b35",
            "icon_name": "cog"
        },
        TYPE_PROFESSIONAL: {
            "color": "#f4981e",
            "icon_name": "industry"
        },
        TYPE_2022: {
            "color": "#571aff",
            "icon_name": "users"
        },
    }

    TYPE_DESCRIPTION = {
        TYPE_LOCAL_GROUP:
        "Les groupes d’action géographiques de la France insoumise sont constitués sur la base d’un"
        " territoire réduit (quartier, villages ou petites villes, cantons). Chaque insoumis⋅e peut assurer"
        " l’animation d’un seul groupe d’action géographique.",
        TYPE_THEMATIC:
        "Les groupes d’action thématiques réunissent des insoumis⋅es qui"
        " souhaitent agir de concert sur un thème donné en lien avec les livrets"
        " thématiques correspondant.",
        TYPE_FUNCTIONAL:
        "Les groupes d’action fonctionnels remplissent"
        " des fonctions précises (formations, organisation"
        " des apparitions publiques, rédaction de tracts, chorale insoumise,"
        " journaux locaux, auto-organisation, etc…).",
        TYPE_PROFESSIONAL:
        "Les groupes d’action professionnels rassemblent des insoumis⋅es qui"
        " souhaitent agir au sein de leur entreprise ou de leur lieu d’étude.",
        TYPE_2022:
        "Les équipes de soutien « Nous Sommes Pour ! » peuvent être rejointes par toutes les personnes "
        "ayant parainné la candidature de Jean-Luc Mélenchon.",
    }

    TYPE_DISABLED_DESCRIPTION = {
        TYPE_LOCAL_GROUP: "",
        TYPE_THEMATIC: "",
        TYPE_FUNCTIONAL: "",
        TYPE_PROFESSIONAL: "",
        TYPE_2022: "✅ Vous animez déjà une équipe de soutien",
    }

    MEMBERSHIP_LIMIT = 30

    objects = SupportGroupQuerySet.as_manager()

    name = models.CharField(_("nom"),
                            max_length=255,
                            blank=False,
                            help_text=_("Le nom du groupe"))

    type = models.CharField(
        _("type de groupe"),
        max_length=1,
        blank=False,
        default=TYPE_LOCAL_GROUP,
        choices=TYPE_CHOICES,
    )

    subtypes = models.ManyToManyField("SupportGroupSubtype",
                                      related_name="supportgroups",
                                      blank=True)

    published = models.BooleanField(
        _("publié"),
        default=True,
        blank=False,
        help_text=_("Le groupe doit-il être visible publiquement."),
    )

    tags = models.ManyToManyField("SupportGroupTag",
                                  related_name="groups",
                                  blank=True)

    members = models.ManyToManyField("people.Person",
                                     related_name="supportgroups",
                                     through="Membership",
                                     blank=True)

    @property
    def managers(self):
        return [
            m.person for m in self.memberships.filter(
                membership_type__gte=Membership.MEMBERSHIP_TYPE_MANAGER)
        ]

    @property
    def referents(self):
        return [
            m.person for m in self.memberships.filter(
                membership_type__gte=Membership.MEMBERSHIP_TYPE_REFERENT)
        ]

    @property
    def events_count(self):
        from agir.events.models import Event

        return self.organized_events.filter(
            visibility=Event.VISIBILITY_PUBLIC).count()

    @property
    def members_count(self):
        return Membership.objects.filter(supportgroup=self).count()

    @property
    def is_full(self):
        return self.is_2022 and self.members_count >= self.MEMBERSHIP_LIMIT

    @property
    def is_certified(self):
        return self.subtypes.filter(
            label__in=settings.CERTIFIED_GROUP_SUBTYPES).exists()

    @property
    def allow_external(self):
        return self.subtypes.filter(allow_external=True).exists()

    @property
    def external_help_text(self):
        subtype = self.subtypes.filter(allow_external=True).first()
        return subtype.external_help_text or ""

    @property
    def is_2022(self):
        return self.type == self.TYPE_2022

    class Meta:
        verbose_name = _("groupe d'action")
        verbose_name_plural = _("groupes d'action")
        ordering = ("-created", )
        permissions = (("view_hidden_supportgroup",
                        _("Peut afficher les groupes non publiés")), )

    def __str__(self):
        return self.name

    def __repr__(self):
        return f"{self.__class__.__name__}(id={str(self.pk)!r}, name={self.name!r})"
Ejemplo n.º 15
0
class Usuario(ExportModelOperationsMixin('usuario'),
              SimpleEmailConfirmationUserMixin, CustomAbstractUser,
              TemChaveExterna):
    """Classe de autenticacao do django, ela tem muitos perfis."""

    SME = 0
    PREFEITURA = 1

    TIPOS_EMAIL = ((SME, '@sme.prefeitura.sp.gov.br'),
                   (PREFEITURA, '@prefeitura.sp.gov.br'))
    nome = models.CharField(_('name'), max_length=150)
    email = models.EmailField(_('email address'), unique=True)
    tipo_email = models.PositiveSmallIntegerField(choices=TIPOS_EMAIL,
                                                  null=True,
                                                  blank=True)

    registro_funcional = models.CharField(
        _('RF'),
        max_length=7,
        blank=True,
        null=True,
        unique=True,  # noqa DJ01
        validators=[MinLengthValidator(7)])

    # TODO: essew atributow deve pertencer somente a um model Pessoa
    cpf = models.CharField(
        _('CPF'),
        max_length=11,
        blank=True,
        null=True,
        unique=True,  # noqa DJ01
        validators=[MinLengthValidator(11)])
    contatos = models.ManyToManyField('dados_comuns.Contato', blank=True)

    # TODO: esses atributos devem pertencer somente a um model Nutricionista
    super_admin_terceirizadas = models.BooleanField(
        'É Administrador por parte das Terceirizadas?', default=False)
    crn_numero = models.CharField('Nutricionista crn',
                                  max_length=160,
                                  blank=True,
                                  null=True)  # noqa DJ01

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []  # type: ignore

    @property
    def vinculos(self):
        return self.vinculos

    @property
    def vinculo_atual(self):
        if self.vinculos.filter(
                Q(data_inicial=None, data_final=None, ativo=False)
                |  # noqa W504 esperando ativacao
                Q(data_inicial__isnull=False, data_final=None, ativo=True)
        ).exists():
            return self.vinculos.get(
                Q(data_inicial=None, data_final=None, ativo=False)
                |  # noqa W504 esperando ativacao
                Q(data_inicial__isnull=False, data_final=None, ativo=True))
        return None

    @property
    def tipo_usuario(self):
        tipo_usuario = 'indefinido'
        if self.vinculo_atual:
            tipo_usuario = self.vinculo_atual.content_type.model
            if tipo_usuario == 'codae':
                if self.vinculo_atual.perfil.nome in [
                        COORDENADOR_GESTAO_ALIMENTACAO_TERCEIRIZADA,
                        ADMINISTRADOR_GESTAO_ALIMENTACAO_TERCEIRIZADA
                ]:
                    tipo_usuario = 'gestao_alimentacao_terceirizada'
                else:
                    tipo_usuario = 'dieta_especial'
        return tipo_usuario

    @property
    def pode_efetuar_cadastro(self):
        # TODO: passar isso para o app EOL serviço
        headers = {'Authorization': f'Token {DJANGO_EOL_API_TOKEN}'}
        r = requests.get(
            f'{DJANGO_EOL_API_URL}/cargos/{self.registro_funcional}',
            headers=headers)
        response = r.json()
        if not isinstance(response, dict):
            raise EolException(f'{response}')
        diretor_de_escola = False
        for result in response['results']:
            if result['cargo'] == 'DIRETOR DE ESCOLA':
                diretor_de_escola = True
                break
        vinculo_aguardando_ativacao = self.vinculo_atual.status == Vinculo.STATUS_AGUARDANDO_ATIVACAO
        return diretor_de_escola or vinculo_aguardando_ativacao

    def enviar_email_confirmacao(self):
        self.add_email_if_not_exists(self.email)
        content = {
            'uuid': self.uuid,
            'confirmation_key': self.confirmation_key
        }
        self.email_user(
            subject='Confirme seu e-mail - SIGPAE',
            message=f'Clique neste link para confirmar seu e-mail no SIGPAE \n'
            f': {url_configs("CONFIRMAR_EMAIL", content)}',
        )

    def enviar_email_recuperacao_senha(self):
        token_generator = PasswordResetTokenGenerator()
        token = token_generator.make_token(self)
        content = {'uuid': self.uuid, 'confirmation_key': token}
        self.email_user(
            subject='Email de recuperação de senha',
            message=f'Clique neste link para criar uma nova senha no SIGPAE \n'
            f': {url_configs("RECUPERAR_SENHA", content)}',
        )

    def enviar_email_administrador(self):
        self.add_email_if_not_exists(self.email)
        self.email_user(
            subject='[SIGPAE] Novo cadastro de empresa',
            message=
            f'Seja bem vindo(a), {self.nome}\n\nSua empresa foi cadastrada no sistema SIGPAE e a partir '
            +
            f'desse momento você terá acesso as suas funcionalidades.\n\nEfetue seu cadastro através do link '
            +
            f'abaixo e acompanhe as suas solicitações.\n\n{url_configs("LOGIN_TERCEIRIZADAS", {})}'
        )

    def atualiza_senha(self, senha, token):
        token_generator = PasswordResetTokenGenerator()
        if token_generator.check_token(self, token):
            self.set_password(senha)
            self.save()
            return True
        return False

    def criar_vinculo_administrador(self, instituicao, nome_perfil):
        perfil = Perfil.objects.get(nome=nome_perfil)
        Vinculo.objects.create(instituicao=instituicao,
                               perfil=perfil,
                               usuario=self,
                               ativo=False)

    class Meta:
        ordering = ('-super_admin_terceirizadas', )
Ejemplo n.º 16
0
class LocalizedString(ExportModelOperationsMixin("localized_string"),
                      models.Model):  # type: ignore # noqa E501
    string_id = models.CharField(max_length=128, unique=True)

    def __str__(self):
        return self.string_id
Ejemplo n.º 17
0
class Domain(ExportModelOperationsMixin('Domain'), models.Model):
    @staticmethod
    def _minimum_ttl_default():
        return settings.MINIMUM_TTL_DEFAULT

    class RenewalState(models.IntegerChoices):
        IMMORTAL = 0
        FRESH = 1
        NOTIFIED = 2
        WARNED = 3

    created = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=191,
                            unique=True,
                            validators=validate_domain_name)
    owner = models.ForeignKey(User,
                              on_delete=models.PROTECT,
                              related_name='domains')
    published = models.DateTimeField(null=True, blank=True)
    minimum_ttl = models.PositiveIntegerField(
        default=_minimum_ttl_default.__func__)
    renewal_state = models.IntegerField(choices=RenewalState.choices,
                                        default=RenewalState.IMMORTAL)
    renewal_changed = models.DateTimeField(auto_now_add=True)
    _keys = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.pk is None and kwargs.get(
                'renewal_state') is None and self.is_locally_registrable:
            self.renewal_state = Domain.RenewalState.FRESH

    @cached_property
    def public_suffix(self):
        try:
            public_suffix = psl.get_public_suffix(self.name)
            is_public_suffix = psl.is_public_suffix(self.name)
        except (Timeout, NoNameservers):
            public_suffix = self.name.rpartition('.')[2]
            is_public_suffix = ('.'
                                not in self.name)  # TLDs are public suffixes
        except psl_dns.exceptions.UnsupportedRule as e:
            # It would probably be fine to treat this as a non-public suffix (with the TLD acting as the
            # public suffix and setting both public_suffix and is_public_suffix accordingly).
            # However, in order to allow to investigate the situation, it's better not catch
            # this exception. For web requests, our error handler turns it into a 503 error
            # and makes sure admins are notified.
            raise e

        if is_public_suffix:
            return public_suffix

        # Take into account that any of the parent domains could be a local public suffix. To that
        # end, identify the longest local public suffix that is actually a suffix of domain_name.
        for local_public_suffix in settings.LOCAL_PUBLIC_SUFFIXES:
            has_local_public_suffix_parent = (
                '.' + self.name).endswith('.' + local_public_suffix)
            if has_local_public_suffix_parent and len(
                    local_public_suffix) > len(public_suffix):
                public_suffix = local_public_suffix

        return public_suffix

    def is_covered_by_foreign_zone(self):
        # Generate a list of all domains connecting this one and its public suffix.
        # If another user owns a zone with one of these names, then the requested
        # domain is unavailable because it is part of the other user's zone.
        private_components = self.name.rsplit(self.public_suffix,
                                              1)[0].rstrip('.')
        private_components = private_components.split(
            '.') if private_components else []
        private_domains = [
            '.'.join(private_components[i:])
            for i in range(0, len(private_components))
        ]
        private_domains = [
            f'{private_domain}.{self.public_suffix}'
            for private_domain in private_domains
        ]
        assert self.name == next(iter(private_domains), self.public_suffix)

        # Determine whether domain is covered by other users' zones
        return Domain.objects.filter(
            Q(name__in=private_domains)
            & ~Q(owner=self._owner_or_none)).exists()

    def covers_foreign_zone(self):
        # Note: This is not completely accurate: Ideally, we should only consider zones with identical public suffix.
        # (If a public suffix lies in between, it's ok.) However, as there could be many descendant zones, the accurate
        # check is expensive, so currently not implemented (PSL lookups for each of them).
        return Domain.objects.filter(
            Q(name__endswith=f'.{self.name}')
            & ~Q(owner=self._owner_or_none)).exists()

    def is_registrable(self):
        """
        Returns False if the domain name is reserved, a public suffix, or covered by / covers another user's domain.
        Otherwise, True is returned.
        """
        self.clean()  # ensure .name is a domain name
        private_generation = self.name.count('.') - self.public_suffix.count(
            '.')
        assert private_generation >= 0

        # .internal is reserved
        if f'.{self.name}'.endswith('.internal'):
            return False

        # Public suffixes can only be registered if they are local
        if private_generation == 0 and self.name not in settings.LOCAL_PUBLIC_SUFFIXES:
            return False

        # Disallow _acme-challenge.dedyn.io and the like. Rejects reserved direct children of public suffixes.
        reserved_prefixes = (
            '_',
            'autoconfig.',
            'autodiscover.',
        )
        if private_generation == 1 and any(
                self.name.startswith(prefix) for prefix in reserved_prefixes):
            return False

        # Domains covered by another user's zone can't be registered
        if self.is_covered_by_foreign_zone():
            return False

        # Domains that would cover another user's zone can't be registered
        if self.covers_foreign_zone():
            return False

        return True

    @property
    def keys(self):
        if not self._keys:
            self._keys = pdns.get_keys(self)
        return self._keys

    @property
    def touched(self):
        try:
            rrset_touched = max(updated
                                for updated in self.rrset_set.values_list(
                                    'touched', flat=True))
            # If the domain has not been published yet, self.published is None and max() would fail
            return rrset_touched if not self.published else max(
                rrset_touched, self.published)
        except ValueError:
            # This can be none if the domain was never published and has no records (but there should be at least NS)
            return self.published

    @property
    def is_locally_registrable(self):
        return self.parent_domain_name in settings.LOCAL_PUBLIC_SUFFIXES

    @property
    def _owner_or_none(self):
        try:
            return self.owner
        except Domain.owner.RelatedObjectDoesNotExist:
            return None

    @property
    def parent_domain_name(self):
        return self._partitioned_name[1]

    @property
    def _partitioned_name(self):
        subname, _, parent_name = self.name.partition('.')
        return subname, parent_name or None

    def save(self, *args, **kwargs):
        self.full_clean(validate_unique=False)
        super().save(*args, **kwargs)

    def update_delegation(self, child_domain: Domain):
        child_subname, child_domain_name = child_domain._partitioned_name
        if self.name != child_domain_name:
            raise ValueError(
                'Cannot update delegation of %s as it is not an immediate child domain of %s.'
                % (child_domain.name, self.name))

        if child_domain.pk:
            # Domain real: set delegation
            child_keys = child_domain.keys
            if not child_keys:
                raise APIException(
                    'Cannot delegate %s, as it currently has no keys.' %
                    child_domain.name)

            RRset.objects.create(domain=self,
                                 subname=child_subname,
                                 type='NS',
                                 ttl=3600,
                                 contents=settings.DEFAULT_NS)
            RRset.objects.create(
                domain=self,
                subname=child_subname,
                type='DS',
                ttl=300,
                contents=[ds for k in child_keys for ds in k['ds']])
            metrics.get('desecapi_autodelegation_created').inc()
        else:
            # Domain not real: remove delegation
            for rrset in self.rrset_set.filter(subname=child_subname,
                                               type__in=['NS', 'DS']):
                rrset.delete()
            metrics.get('desecapi_autodelegation_deleted').inc()

    def delete(self):
        ret = super().delete()
        logger.warning(f'Domain {self.name} deleted (owner: {self.owner.pk})')
        return ret

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('created', )
Ejemplo n.º 18
0
class Payment(ExportModelOperationsMixin("payment"), TimeStampedModel,
              LocationMixin):
    objects = PaymentManager()

    STATUS_WAITING = 0
    STATUS_COMPLETED = 1
    STATUS_ABANDONED = 2
    STATUS_CANCELED = 3
    STATUS_REFUSED = 4

    STATUS_CHOICES = (
        (STATUS_WAITING, "En attente"),
        (STATUS_COMPLETED, "Terminé"),
        (STATUS_ABANDONED, "Abandonné"),
        (STATUS_CANCELED, "Annulé"),
        (STATUS_REFUSED, "Refusé"),
    )

    person = models.ForeignKey("people.Person",
                               on_delete=models.SET_NULL,
                               null=True,
                               related_name="payments")

    email = models.EmailField("email", max_length=255)
    first_name = models.CharField("prénom", max_length=255)
    last_name = models.CharField("nom de famille", max_length=255)
    phone_number = PhoneNumberField("numéro de téléphone", null=True)

    type = models.CharField("type",
                            choices=get_payment_choices(),
                            max_length=255)
    mode = models.CharField(_("Mode de paiement"),
                            max_length=70,
                            null=False,
                            blank=False)

    price = models.IntegerField(_("prix en centimes d'euros"))
    status = models.IntegerField("status",
                                 choices=STATUS_CHOICES,
                                 default=STATUS_WAITING)
    meta = JSONField(blank=True, default=dict)
    events = JSONField(_("Événements de paiement"), blank=True, default=list)

    def get_price_display(self):
        return "{} €".format(floatformat(self.price / 100, 2))

    def get_mode_display(self):
        return (PAYMENT_MODES[self.mode].label
                if self.mode in PAYMENT_MODES else self.mode)

    def get_payment_url(self):
        return reverse("payment_page", args=[self.pk])

    def can_retry(self):
        return (self.mode in PAYMENT_MODES
                and PAYMENT_MODES[self.mode].can_retry
                and self.status != self.STATUS_COMPLETED)

    def can_cancel(self):
        return (self.mode in PAYMENT_MODES
                and PAYMENT_MODES[self.mode].can_cancel
                and self.status != self.STATUS_COMPLETED)

    def html_full_address(self):
        return display_address(self)

    def description(self):
        from .actions import description_for_payment

        return description_for_payment(self)

    def __str__(self):
        return _("Paiement n°") + str(self.id)

    def __repr__(self):
        return "{klass}(email={email!r}, status={status!r}, type={type!r}, mode={mode!r}, price={price!r}".format(
            klass=self.__class__.__name__,
            email=self.email,
            status=self.status,
            type=self.type,
            mode=self.mode,
            price=self.price,
        )

    class Meta:
        get_latest_by = "created"
Ejemplo n.º 19
0
class User(ExportModelOperationsMixin('User'), AbstractBaseUser):
    @staticmethod
    def _limit_domains_default():
        return settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = CIEmailField(
        verbose_name='email address',
        unique=True,
    )
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)
    limit_domains = models.IntegerField(
        default=_limit_domains_default.__func__, null=True, blank=True)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

    def __str__(self):
        return self.email

    # noinspection PyMethodMayBeStatic
    def has_perm(self, *_):
        """Does the user have a specific permission?"""
        # Simplest possible answer: Yes, always
        return True

    # noinspection PyMethodMayBeStatic
    def has_module_perms(self, *_):
        """Does the user have permissions to view the app `app_label`?"""
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        """Is the user a member of staff?"""
        # Simplest possible answer: All admins are staff
        return self.is_admin

    def activate(self):
        self.is_active = True
        self.save()

    def change_email(self, email):
        old_email = self.email
        self.email = email
        self.validate_unique()
        self.save()

        self.send_email('change-email-confirmation-old-email',
                        recipient=old_email)

    def change_password(self, raw_password):
        self.set_password(raw_password)
        self.save()
        self.send_email('password-change-confirmation')

    def delete(self):
        pk = self.pk
        ret = super().delete()
        logger.warning(f'User {pk} deleted')
        return ret

    def send_email(self, reason, context=None, recipient=None):
        fast_lane = 'email_fast_lane'
        slow_lane = 'email_slow_lane'
        immediate_lane = 'email_immediate_lane'
        lanes = {
            'activate': slow_lane,
            'activate-with-domain': slow_lane,
            'change-email': slow_lane,
            'change-email-confirmation-old-email': fast_lane,
            'password-change-confirmation': fast_lane,
            'reset-password': fast_lane,
            'delete-user': fast_lane,
            'domain-dyndns': fast_lane,
            'renew-domain': immediate_lane,
        }
        if reason not in lanes:
            raise ValueError(
                f'Cannot send email to user {self.pk} without a good reason: {reason}'
            )

        context = context or {}
        content = get_template(f'emails/{reason}/content.txt').render(context)
        content += f'\nSupport Reference: user_id = {self.pk}\n'
        footer = get_template('emails/footer.txt').render()

        logger.warning(
            f'Queuing email for user account {self.pk} (reason: {reason}, lane: {lanes[reason]})'
        )
        num_queued = EmailMessage(
            subject=get_template(f'emails/{reason}/subject.txt').render(
                context).strip(),
            body=content + footer,
            from_email=get_template('emails/from.txt').render(),
            to=[recipient or self.email],
            connection=get_connection(lane=lanes[reason],
                                      debug={
                                          'user': self.pk,
                                          'reason': reason
                                      })).send()
        metrics.get('desecapi_messages_queued').labels(
            reason, self.pk, lanes[reason]).observe(num_queued)
        return num_queued
Ejemplo n.º 20
0
class Terceirizada(ExportModelOperationsMixin('terceirizada'), TemChaveExterna,
                   Ativavel, TemIdentificadorExternoAmigavel, TemVinculos):
    # Tipo Empresa Choice
    ARMAZEM_DISTRIBUIDOR = 'ARMAZEM/DISTRIBUIDOR'
    FORNECEDOR_DISTRIBUIDOR = 'FORNECEDOR/DISTRIBUIDOR'
    # opção se faz necessária para atender o fluxo do alimentação terceirizada
    TERCEIRIZADA = 'TERCEIRIZADA'

    TIPO_EMPRESA_CHOICES = (
        (ARMAZEM_DISTRIBUIDOR, 'Armazém/Distribuidor'),
        (FORNECEDOR_DISTRIBUIDOR, 'Fornecedor/Distribuidor'),
        (TERCEIRIZADA, 'Terceirizada'),
    )
    # Tipo Alimento Choice
    TIPO_ALIMENTO_CONGELADOS = 'CONGELADOS_E_RESFRIADOS'
    TIPO_ALIMENTO_FLVO = 'FLVO'
    TIPO_ALIMENTO_PAES_E_BOLO = 'PAES_E_BOLO'
    TIPO_ALIMENTO_SECOS = 'SECOS'
    # opção se faz necessária para atender o fluxo do alimentação terceirizada
    TIPO_ALIMENTO_TERCEIRIZADA = 'TERCEIRIZADA'

    TIPO_ALIMENTO_NOMES = {
        TIPO_ALIMENTO_CONGELADOS: 'Congelados e resfriados',
        TIPO_ALIMENTO_FLVO: 'FLVO',
        TIPO_ALIMENTO_PAES_E_BOLO: 'Pães & bolos',
        TIPO_ALIMENTO_SECOS: 'Secos',
        TIPO_ALIMENTO_TERCEIRIZADA: 'Terceirizada',
    }

    TIPO_ALIMENTO_CHOICES = (
        (TIPO_ALIMENTO_CONGELADOS,
         TIPO_ALIMENTO_NOMES[TIPO_ALIMENTO_CONGELADOS]),
        (TIPO_ALIMENTO_FLVO, TIPO_ALIMENTO_NOMES[TIPO_ALIMENTO_FLVO]),
        (TIPO_ALIMENTO_PAES_E_BOLO,
         TIPO_ALIMENTO_NOMES[TIPO_ALIMENTO_PAES_E_BOLO]),
        (TIPO_ALIMENTO_SECOS, TIPO_ALIMENTO_NOMES[TIPO_ALIMENTO_SECOS]),
    )

    nome_fantasia = models.CharField('Nome fantasia',
                                     max_length=160,
                                     blank=True)
    razao_social = models.CharField('Razao social', max_length=160, blank=True)
    cnpj = models.CharField('CNPJ',
                            validators=[MinLengthValidator(14)],
                            max_length=14)
    representante_legal = models.CharField('Representante legal',
                                           max_length=160,
                                           blank=True)
    representante_telefone = models.CharField(
        'Representante contato (telefone)', max_length=160, blank=True)
    representante_email = models.CharField('Representante contato (email)',
                                           max_length=160,
                                           blank=True)
    endereco = models.CharField('Endereco', max_length=160, blank=True)
    cep = models.CharField('CEP', max_length=8, blank=True)
    bairro = models.CharField('Bairro', max_length=150, blank=True)
    cidade = models.CharField('Cidade', max_length=150, blank=True)
    estado = models.CharField('Estado', max_length=150, blank=True)
    numero = models.CharField('Número', max_length=10, blank=True)
    complemento = models.CharField('Complemento', max_length=50, blank=True)
    eh_distribuidor = models.BooleanField('É distribuidor?', default=False)
    responsavel_nome = models.CharField('Responsável',
                                        max_length=160,
                                        blank=True)
    responsavel_email = models.CharField('Responsável contato (email)',
                                         max_length=160,
                                         blank=True)
    responsavel_cpf = models.CharField(
        max_length=11,
        blank=True,
        null=True,
        unique=True,  # noqa DJ01
        validators=[MinLengthValidator(11)])
    responsavel_telefone = models.CharField('Responsável contato (telefone)',
                                            max_length=160,
                                            blank=True)
    responsavel_cargo = models.CharField('Responsável cargo',
                                         max_length=50,
                                         blank=True)
    # OBS.: Uso exclusivo do modulo de abastecimento(logistica).
    # Não tem relação com o processo do edital com associação de contrato a empresa.
    numero_contrato = models.CharField('Número de contrato',
                                       max_length=50,
                                       blank=True)
    tipo_empresa = models.CharField(choices=TIPO_EMPRESA_CHOICES,
                                    max_length=25,
                                    default=TERCEIRIZADA)
    tipo_alimento = models.CharField(choices=TIPO_ALIMENTO_CHOICES,
                                     max_length=25,
                                     default=TIPO_ALIMENTO_TERCEIRIZADA)
    criado_em = models.DateTimeField('Criado em',
                                     editable=False,
                                     auto_now_add=True)

    # TODO: criar uma tabela central (Instituição) para agregar Escola, DRE, Terc e CODAE???
    # e a partir dai a instituição que tem contatos e endereço?
    # o mesmo para pessoa fisica talvez?
    contatos = models.ManyToManyField('dados_comuns.Contato', blank=True)

    @property
    def vinculos_que_podem_ser_finalizados(self):
        return self.vinculos.filter(
            Q(data_inicial=None, data_final=None, ativo=False)
            |  # noqa W504 esperando ativacao
            Q(data_inicial__isnull=False, data_final=None,
              ativo=True)  # noqa W504 ativo
        ).exclude(perfil__nome=NUTRI_ADMIN_RESPONSAVEL)

    def desvincular_lotes(self):
        for lote in self.lotes.all():
            self.lotes.remove(lote)
        self.save()

    @property
    def quantidade_alunos(self):
        quantidade_total = 0
        for lote in self.lotes.all():
            quantidade_total += lote.quantidade_alunos
        return quantidade_total

    @property
    def nome(self):
        return self.nome_fantasia

    @property
    def nutricionistas(self):
        return self.nutricionistas

    @property
    def super_admin(self):
        vinculo = self.vinculos.filter(
            usuario__super_admin_terceirizadas=True).last()
        if vinculo:
            return vinculo.usuario
        return None

    @property
    def inclusoes_continuas_autorizadas(self):
        return InclusaoAlimentacaoContinua.objects.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
                InclusaoAlimentacaoContinua.workflow_class.
                TERCEIRIZADA_TOMOU_CIENCIA
            ])

    @property
    def inclusoes_normais_autorizadas(self):
        return GrupoInclusaoAlimentacaoNormal.objects.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                GrupoInclusaoAlimentacaoNormal.workflow_class.CODAE_AUTORIZADO,
                GrupoInclusaoAlimentacaoNormal.workflow_class.
                TERCEIRIZADA_TOMOU_CIENCIA
            ])

    @property
    def inclusoes_continuas_reprovadas(self):
        return InclusaoAlimentacaoContinua.objects.filter(
            escola__lote__in=self.lotes.all(),
            status=InclusaoAlimentacaoContinua.workflow_class.
            CODAE_NEGOU_PEDIDO)

    @property
    def solicitacao_kit_lanche_avulsa_autorizadas(self):
        return SolicitacaoKitLancheAvulsa.objects.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                SolicitacaoKitLancheAvulsa.workflow_class.CODAE_AUTORIZADO,
                SolicitacaoKitLancheAvulsa.workflow_class.
                TERCEIRIZADA_TOMOU_CIENCIA
            ])

    @property
    def inclusoes_normais_reprovadas(self):
        return GrupoInclusaoAlimentacaoNormal.objects.filter(
            escola__lote__in=self.lotes.all(),
            status=GrupoInclusaoAlimentacaoNormal.workflow_class.
            CODAE_NEGOU_PEDIDO)

    # TODO: talvez fazer um manager genérico pra fazer esse filtro

    def inclusoes_continuas_das_minhas_escolas_no_prazo_vencendo(
            self, filtro_aplicado):
        if filtro_aplicado == 'hoje':
            # TODO: rever filtro hoje que nao é mais usado
            inclusoes_continuas = InclusaoAlimentacaoContinua.objects
        else:  # se o filtro nao for hoje, filtra o padrao
            inclusoes_continuas = InclusaoAlimentacaoContinua.vencidos
        return inclusoes_continuas.filter(
            status=InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def inclusoes_continuas_das_minhas_escolas_no_prazo_limite(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            inclusoes_continuas = InclusaoAlimentacaoContinua.desta_semana
        else:
            inclusoes_continuas = InclusaoAlimentacaoContinua.objects  # type: ignore
        return inclusoes_continuas.filter(
            status=InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def inclusoes_continuas_das_minhas_escolas_no_prazo_regular(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_30_dias':
            inclusoes_continuas = InclusaoAlimentacaoContinua.deste_mes
        elif filtro_aplicado == 'daqui_a_7_dias':
            inclusoes_continuas = InclusaoAlimentacaoContinua.desta_semana  # type: ignore
        else:
            inclusoes_continuas = InclusaoAlimentacaoContinua.objects  # type: ignore
        return inclusoes_continuas.filter(
            status=InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def inclusoes_normais_das_minhas_escolas_no_prazo_vencendo(
            self, filtro_aplicado):
        if filtro_aplicado == 'hoje':
            # TODO: rever filtro hoje que nao é mais usado
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.objects
        else:
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.vencidos
        return inclusoes_normais.filter(
            status=InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def inclusoes_normais_das_minhas_escolas_no_prazo_limite(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.desta_semana
        else:
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.objects  # type: ignore
        return inclusoes_normais.filter(
            status=InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def inclusoes_normais_das_minhas_escolas_no_prazo_regular(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_30_dias':
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.deste_mes
        elif filtro_aplicado == 'daqui_a_7_dias':
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.desta_semana  # type: ignore
        else:
            inclusoes_normais = GrupoInclusaoAlimentacaoNormal.objects  # type: ignore
        return inclusoes_normais.filter(
            status=InclusaoAlimentacaoContinua.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def alteracoes_cardapio_das_minhas_escolas_no_prazo_vencendo(
            self, filtro_aplicado):
        if filtro_aplicado == 'hoje':
            # TODO: rever filtro hoje que nao é mais usado
            alteracoes_cardapio = AlteracaoCardapio.objects
        else:
            alteracoes_cardapio = AlteracaoCardapio.vencidos
        return alteracoes_cardapio.filter(
            status=AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def alteracoes_cardapio_das_minhas_escolas_no_prazo_limite(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            alteracoes_cardapio = AlteracaoCardapio.desta_semana
        else:
            alteracoes_cardapio = AlteracaoCardapio.objects  # type: ignore
        return alteracoes_cardapio.filter(
            status=AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def alteracoes_cardapio_das_minhas_escolas_no_prazo_regular(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_30_dias':
            alteracoes_cardapio = AlteracaoCardapio.deste_mes
        elif filtro_aplicado == 'daqui_a_7_dias':
            alteracoes_cardapio = AlteracaoCardapio.desta_semana  # type: ignore
        else:
            alteracoes_cardapio = AlteracaoCardapio.objects  # type: ignore
        return alteracoes_cardapio.filter(
            status=AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def alteracoes_cardapio_das_minhas(self, filtro_aplicado):
        queryset = queryset_por_data(filtro_aplicado, AlteracaoCardapio)
        return queryset.filter(status__in=[
            AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
            AlteracaoCardapio.workflow_class.CODAE_QUESTIONADO
        ],
                               escola__lote__in=self.lotes.all())

    def alteracoes_cardapio_cei_das_minhas(self, filtro_aplicado):
        queryset = queryset_por_data(filtro_aplicado, AlteracaoCardapioCEI)
        return queryset.filter(status__in=[
            AlteracaoCardapioCEI.workflow_class.CODAE_AUTORIZADO,
            AlteracaoCardapioCEI.workflow_class.CODAE_QUESTIONADO
        ],
                               escola__lote__in=self.lotes.all())

    def grupos_inclusoes_alimentacao_normal_das_minhas_escolas(
            self, filtro_aplicado):
        queryset = queryset_por_data(filtro_aplicado,
                                     GrupoInclusaoAlimentacaoNormal)
        return queryset.filter(
            status=AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def inclusoes_alimentacao_de_cei_das_minhas_escolas(self, filtro_aplicado):
        return self.filtra_solicitacoes_minhas_escolas_a_validar_por_data(
            filtro_aplicado, InclusaoAlimentacaoDaCEI)

    def inclusoes_alimentacao_continua_das_minhas_escolas(
            self, filtro_aplicado):
        queryset = queryset_por_data(filtro_aplicado,
                                     InclusaoAlimentacaoContinua)
        return queryset.filter(
            status=AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
            escola__lote__in=self.lotes.all())

    def suspensoes_alimentacao_das_minhas_escolas(self, filtro_aplicado):
        queryset = queryset_por_data(filtro_aplicado,
                                     GrupoSuspensaoAlimentacao)
        return queryset.filter(
            status=GrupoSuspensaoAlimentacao.workflow_class.INFORMADO,
            escola__lote__in=self.lotes.all())

    @property
    def alteracoes_cardapio_autorizadas(self):
        return AlteracaoCardapio.objects.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                AlteracaoCardapio.workflow_class.CODAE_AUTORIZADO,
                AlteracaoCardapio.workflow_class.TERCEIRIZADA_TOMOU_CIENCIA
            ])

    @property
    def alteracoes_cardapio_reprovadas(self):
        return AlteracaoCardapio.objects.filter(
            escola__lote__in=self.lotes.all(),
            status=AlteracaoCardapio.workflow_class.CODAE_NEGOU_PEDIDO)

    #
    # Inversão de dia de cardápio
    #

    def inversoes_cardapio_das_minhas_escolas(self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            inversoes_cardapio = InversaoCardapio.desta_semana
        elif filtro_aplicado == 'daqui_a_30_dias':
            inversoes_cardapio = InversaoCardapio.deste_mes  # type: ignore
        else:
            inversoes_cardapio = InversaoCardapio.objects  # type: ignore
        return inversoes_cardapio.filter(
            escola__lote__in=self.lotes.all(),
            status=InversaoCardapio.workflow_class.CODAE_AUTORIZADO)

    @property
    def inversoes_cardapio_autorizadas(self):
        return InversaoCardapio.objects.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                InversaoCardapio.workflow_class.CODAE_AUTORIZADO,
                InversaoCardapio.workflow_class.TERCEIRIZADA_TOMOU_CIENCIA
            ])

    #
    # Solicitação Unificada
    #

    def solicitacoes_unificadas_das_minhas_escolas(self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            solicitacoes_unificadas = SolicitacaoKitLancheUnificada.desta_semana
        elif filtro_aplicado == 'daqui_a_30_dias':
            solicitacoes_unificadas = SolicitacaoKitLancheUnificada.deste_mes  # type: ignore
        else:
            solicitacoes_unificadas = SolicitacaoKitLancheUnificada.objects  # type: ignore
        return solicitacoes_unificadas.filter(
            escolas_quantidades__escola__lote__in=self.lotes.all(),
            status=SolicitacaoKitLancheUnificada.workflow_class.
            CODAE_AUTORIZADO).distinct()

    @property
    def solicitacoes_unificadas_autorizadas(self):
        return SolicitacaoKitLancheUnificada.objects.filter(
            escolas_quantidades__escola__lote__in=self.lotes.all(),
            status__in=[
                SolicitacaoKitLancheUnificada.workflow_class.CODAE_AUTORIZADO,
                SolicitacaoKitLancheUnificada.workflow_class.
                TERCEIRIZADA_TOMOU_CIENCIA
            ]).distinct()

    def solicitacoes_kit_lanche_das_minhas_escolas_a_validar(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            solicitacoes_kit_lanche = SolicitacaoKitLancheAvulsa.desta_semana
        elif filtro_aplicado == 'daqui_a_30_dias':
            solicitacoes_kit_lanche = SolicitacaoKitLancheAvulsa.deste_mes  # type: ignore
        else:
            solicitacoes_kit_lanche = SolicitacaoKitLancheAvulsa.objects  # type: ignore
        return solicitacoes_kit_lanche.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                SolicitacaoKitLancheAvulsa.workflow_class.CODAE_AUTORIZADO,
                SolicitacaoKitLancheAvulsa.workflow_class.CODAE_QUESTIONADO
            ])

    def solicitacoes_kit_lanche_cei_das_minhas_escolas_a_validar(
            self, filtro_aplicado):
        if filtro_aplicado == 'daqui_a_7_dias':
            solicitacoes_kit_lanche = SolicitacaoKitLancheCEIAvulsa.desta_semana
        elif filtro_aplicado == 'daqui_a_30_dias':
            solicitacoes_kit_lanche = SolicitacaoKitLancheCEIAvulsa.deste_mes  # type: ignore
        else:
            solicitacoes_kit_lanche = SolicitacaoKitLancheCEIAvulsa.objects  # type: ignore
        return solicitacoes_kit_lanche.filter(
            escola__lote__in=self.lotes.all(),
            status__in=[
                SolicitacaoKitLancheCEIAvulsa.workflow_class.CODAE_AUTORIZADO,
                SolicitacaoKitLancheCEIAvulsa.workflow_class.CODAE_QUESTIONADO
            ])

    def __str__(self):
        return f'{self.nome_fantasia}'

    class Meta:
        verbose_name = 'Terceirizada'
        verbose_name_plural = 'Terceirizadas'
Ejemplo n.º 21
0
class Dog(ExportModelOperationsMixin("dog"), Model):
    name = CharField(max_length=100, unique=True)
    breed = CharField(max_length=100, blank=True, null=True)
    age = PositiveIntegerField(blank=True, null=True)
Ejemplo n.º 22
0
class SubmissionCode(ExportModelOperationsMixin('submission_code'),
                     models.Model):
    submission = models.ForeignKey(Submission,
                                   related_name='codes',
                                   on_delete=models.CASCADE)
    language = CodingLanguageField()
    code = models.TextField()
    summary = models.TextField(blank=True)
    score = models.IntegerField(null=True, blank=True)
    date_submitted = models.DateTimeField(default=timezone.now)
    date_corrected = models.DateTimeField(null=True, blank=True)
    celery_task_id = models.CharField(max_length=128, blank=True)
    result = MsgpackField(null=True, blank=True)

    def done(self):
        return not self.correctable() or self.score is not None

    def succeeded(self):
        return self.done() and self.score is not None and self.score > 0

    def language_enum(self) -> Language:
        return Language[self.language]

    def correctable(self):
        return self.language_enum().correctable()

    def has_result(self):
        if not self.correctable():
            return False
        if not self.done():
            return False
        if not self.celery_task_id or not self.date_corrected:
            return False
        return self.result is not None

    def status(self):
        return _("Pending") if not self.done() else _("Corrected")

    def correction_results(self):
        if not self.celery_task_id or not self.date_corrected:
            return None
        if self.result is None:
            return None
        return Result.parse(self.submission.problem_model(), self.result)

    def request_printable(self):
        req = self.generate_request()
        req = rec_truncate(req, maxlen=100)
        return pprint.pformat(req, width=72)

    def result_printable(self):
        rep = rec_truncate(self.result, maxlen=100)
        return pprint.pformat(rep, width=72)

    def get_absolute_url(self):
        problem = self.submission.problem_model()
        return reverse('problems:submission',
                       kwargs={
                           'year': problem.challenge.year,
                           'type': problem.challenge.event_type.name,
                           'problem': problem.name,
                           'submission': self.id,
                       })

    def generate_request(self) -> dict:
        """Generate a camisole request for the SubmissionCode."""
        problem = self.submission.problem_model()

        def build_tests():
            for ref in problem.tests:
                yield {'name': ref.name, 'stdin': ref.stdin}

        language = self.language_enum()
        request = {
            'lang': language.value.camisole_name,
            'source': self.code,
            'all_fatal': True,
            'execute': problem.execution_limits(language),
            # FIXME: this is arbitrary
            'compile': {
                'mem': int(1e7),
                'time': 20,
                'wall-time': 60,
                'fsize': 20000
            },
            'tests': list(build_tests()),
        }
        return request

    def __str__(self):
        return "{} in {} (score: {})".format(
            self.submission,
            self.language_enum().name_display(),
            self.score if self.succeeded() else self.status())

    class Meta:
        verbose_name = _("Submission code")
        verbose_name_plural = _("Submission codes")
        get_latest_by = 'date_submitted'
        ordering = ('-' + get_latest_by, '-pk')
Ejemplo n.º 23
0
class RSVP(ExportModelOperationsMixin("rsvp"), TimeStampedModel):
    """
    Model that represents a RSVP for one person for an event.
    
    An additional field indicates if the person is bringing any guests with her
    """

    STATUS_AWAITING_PAYMENT = "AP"
    STATUS_CONFIRMED = "CO"
    STATUS_CANCELED = "CA"
    STATUS_CHOICES = (
        (STATUS_AWAITING_PAYMENT, _("En attente du paiement")),
        (STATUS_CONFIRMED, _("Inscription confirmée")),
        (STATUS_CANCELED, _("Inscription annulée")),
    )

    objects = RSVPQuerySet.as_manager()

    person = models.ForeignKey(
        "people.Person", related_name="rsvps", on_delete=models.CASCADE, editable=False
    )
    event = models.ForeignKey(
        "Event", related_name="rsvps", on_delete=models.CASCADE, editable=False
    )
    guests = models.PositiveIntegerField(
        _("nombre d'invités supplémentaires"), default=0, null=False
    )

    payment = models.OneToOneField(
        "payments.Payment",
        on_delete=models.SET_NULL,
        null=True,
        editable=False,
        related_name="rsvp",
    )
    form_submission = models.OneToOneField(
        "people.PersonFormSubmission",
        on_delete=models.SET_NULL,
        null=True,
        editable=False,
        related_name="rsvp",
    )
    guests_form_submissions = models.ManyToManyField(
        "people.PersonFormSubmission",
        related_name="guest_rsvp",
        through="IdentifiedGuest",
    )

    status = models.CharField(
        _("Statut"),
        max_length=2,
        default=STATUS_CONFIRMED,
        choices=STATUS_CHOICES,
        blank=False,
    )

    notifications_enabled = models.BooleanField(
        _("Recevoir les notifications"), default=True
    )

    jitsi_meeting = models.ForeignKey(
        "JitsiMeeting",
        related_name="rsvps",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    class Meta:
        verbose_name = "RSVP"
        verbose_name_plural = "RSVP"
        unique_together = ("event", "person")

    def __str__(self):
        info = "{person} --> {event} ({guests} invités)".format(
            person=self.person, event=self.event, guests=self.guests
        )

        if self.status == RSVP.STATUS_AWAITING_PAYMENT or any(
            guest.status == RSVP.STATUS_AWAITING_PAYMENT
            for guest in self.identified_guests.all()
        ):
            info = info + " paiement(s) en attente"

        return info
Ejemplo n.º 24
0
class Category(ExportModelOperationsMixin('category'), models.Model):
    name = models.CharField(max_length=20)

    def category(self):
        return self.name
Ejemplo n.º 25
0
class Person(
        AbstractSubscriber,
        ExportModelOperationsMixin("person"),
        BaseAPIResource,
        NationBuilderResource,
        LocationMixin,
):
    """
    Model that represents a physical person that signed as a JLM2017 supporter

    A person is identified by the email address he's signed up with.
    He is associated with permissions that determine what he can and cannot do
    with the API.

    He has an optional password, which will be only used to authenticate him with
    the API admin.
    """

    objects = PersonManager()

    role = models.OneToOneField(
        "authentication.Role",
        on_delete=models.PROTECT,
        related_name="person",
        null=True,
    )
    auto_login_salt = models.CharField(max_length=255, blank=True, default="")

    is_insoumise = models.BooleanField(_("Insoumis⋅e"), default=False)
    is_2022 = models.BooleanField(_("Soutien 2022"), default=False)

    MEMBRE_RESEAU_INCONNU = "I"
    MEMBRE_RESEAU_SOUHAITE = "S"
    MEMBRE_RESEAU_OUI = "O"
    MEMBRE_RESEAU_NON = "N"
    MEMBRE_RESEAU_EXCLUS = "E"
    MEMBRE_RESEAU_CHOICES = (
        (MEMBRE_RESEAU_INCONNU, "Inconnu / Non pertinent"),
        (MEMBRE_RESEAU_SOUHAITE, "Souhaite faire partie du réseau des élus"),
        (MEMBRE_RESEAU_OUI, "Fait partie du réseau des élus"),
        (MEMBRE_RESEAU_NON, "Ne souhaite pas faire partie du réseau des élus"),
        (MEMBRE_RESEAU_EXCLUS, "Exclus du réseau"),
    )
    membre_reseau_elus = models.CharField(
        _("Membre du réseau des élus"),
        max_length=1,
        blank=False,
        null=False,
        choices=MEMBRE_RESEAU_CHOICES,
        default=MEMBRE_RESEAU_INCONNU,
        help_text=
        "Pertinent uniquement si la personne a un ou plusieurs mandats électoraux.",
    )

    NEWSLETTER_LFI = "LFI"
    NEWSLETTER_2022 = "2022"
    NEWSLETTER_2022_EXCEPTIONNEL = "2022_exceptionnel"
    NEWSLETTER_2022_EN_LIGNE = "2022_en_ligne"
    NEWSLETTER_2022_CHEZ_MOI = "2022_chez_moi"
    NEWSLETTER_2022_PROGRAMME = "2022_programme"
    NEWSLETTERS_CHOICES = (
        (NEWSLETTER_LFI, "Lettre d'information de la France insoumise"),
        (NEWSLETTER_2022, "Lettre d'information NSP"),
        (NEWSLETTER_2022_EXCEPTIONNEL, "NSP : informations exceptionnelles"),
        (NEWSLETTER_2022_EN_LIGNE, "NSP actions en ligne"),
        (NEWSLETTER_2022_CHEZ_MOI, "NSP agir près de chez moi"),
        (NEWSLETTER_2022_PROGRAMME, "NSP processus programme"),
    )

    newsletters = ChoiceArrayField(
        models.CharField(choices=NEWSLETTERS_CHOICES, max_length=255),
        default=list,
        blank=True,
    )

    subscribed_sms = models.BooleanField(
        _("Recevoir les SMS d'information"),
        default=True,
        blank=True,
        help_text=
        _("Vous recevrez des SMS de la France insoumise comme des meeting près de chez vous ou des appels à volontaire..."
          ),
    )

    event_notifications = models.BooleanField(
        _("Recevoir les notifications des événements"),
        default=True,
        blank=True,
        help_text=_(
            "Vous recevrez des messages quand les informations des évènements auxquels vous souhaitez participer"
            " sont mis à jour ou annulés."),
    )

    group_notifications = models.BooleanField(
        _("Recevoir les notifications de mes groupes"),
        default=True,
        blank=True,
        help_text=_(
            "Vous recevrez des messages quand les informations du groupe change, ou quand le groupe organise des"
            " événements."),
    )

    draw_participation = models.BooleanField(
        _("Participer aux tirages au sort"),
        default=False,
        blank=True,
        help_text=
        _("Vous pourrez être tiré⋅e au sort parmis les Insoumis⋅es pour participer à des événements comme la Convention."
          "Vous aurez la possibilité d'accepter ou de refuser cette participation."
          ),
    )

    first_name = models.CharField(_("prénom"), max_length=255, blank=True)
    last_name = models.CharField(_("nom de famille"),
                                 max_length=255,
                                 blank=True)

    tags = models.ManyToManyField("PersonTag",
                                  related_name="people",
                                  blank=True)

    CONTACT_PHONE_UNVERIFIED = "U"
    CONTACT_PHONE_VERIFIED = "V"
    CONTACT_PHONE_PENDING = "P"
    CONTACT_PHONE_STATUS_CHOICES = (
        (CONTACT_PHONE_UNVERIFIED, _("Non vérifié")),
        (CONTACT_PHONE_VERIFIED, _("Vérifié")),
        (CONTACT_PHONE_PENDING, _("En attente de validation manuelle")),
    )

    contact_phone = ValidatedPhoneNumberField(
        _("Numéro de téléphone de contact"),
        blank=True,
        validated_field_name="contact_phone_status",
        unverified_value=CONTACT_PHONE_UNVERIFIED,
    )
    contact_phone_status = models.CharField(
        _("Statut du numéro de téléphone"),
        choices=CONTACT_PHONE_STATUS_CHOICES,
        max_length=1,
        default=CONTACT_PHONE_UNVERIFIED,
        help_text=_(
            "Pour les numéros hors France métropolitaine, merci de les indiquer sous la forme internationale,"
            " en les préfixant par '+' et le code du pays."),
    )

    GENDER_FEMALE = "F"
    GENDER_MALE = "M"
    GENDER_OTHER = "O"
    GENDER_CHOICES = (
        (GENDER_FEMALE, _("Femme")),
        (GENDER_MALE, _("Homme")),
        (GENDER_OTHER, _("Autre/Non défini")),
    )
    gender = models.CharField(_("Genre"),
                              max_length=1,
                              blank=True,
                              choices=GENDER_CHOICES)
    date_of_birth = models.DateField(_("Date de naissance"),
                                     null=True,
                                     blank=True)

    mandates = MandatesField(_("Mandats électoraux"), default=list, blank=True)

    meta = JSONField(_("Autres données"), default=dict, blank=True)

    commentaires = models.TextField(
        "Commentaires",
        blank=True,
        help_text=
        "ATTENTION : en cas de demande d'accès à ses données par la personne concernée par cette fiche, le"
        " contenu de ce champ lui sera communiqué. N'indiquez ici que des éléments factuels.",
    )

    search = SearchVectorField("Données de recherche",
                               editable=False,
                               null=True)

    class Meta:
        verbose_name = _("personne")
        verbose_name_plural = _("personnes")
        ordering = ("-created", )
        # add permission 'view'
        default_permissions = ("add", "change", "delete", "view")
        permissions = [(
            "select_person",
            "Peut lister pour sélectionner (dans un Select 2 par exemple)",
        )]
        indexes = (
            GinIndex(fields=["search"], name="search_index"),
            models.Index(fields=["contact_phone"], name="contact_phone_index"),
        )

    def save(self, *args, **kwargs):
        if self._state.adding:
            metrics.subscriptions.inc()

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

    def __str__(self):
        if self.first_name and self.last_name:
            return "{} {} <{}>".format(self.first_name, self.last_name,
                                       self.email)
        else:
            return self.email or "<pas d'email>"

    def __repr__(self):
        return f"{self.__class__.__name__}(pk={self.pk!r}, email={self.email})"

    @property
    def email(self):
        return self.primary_email.address if self.primary_email else ""

    @cached_property
    def primary_email(self):
        return self.emails.filter(
            _bounced=False).first() or self.emails.first()

    @property
    def bounced(self):
        return self.primary_email.bounced

    @bounced.setter
    def bounced(self, value):
        self.primary_email.bounced = value
        self.primary_email.save()

    @property
    def bounced_date(self):
        return self.primary_email.bounced_date

    @bounced_date.setter
    def bounced_date(self, value):
        self.primary_email.bounced_date = value
        self.primary_email.save()

    @property
    def subscribed(self):
        return self.NEWSLETTER_LFI in self.newsletters

    @subscribed.setter
    def subscribed(self, value):
        if value and not self.subscribed:
            self.newsletters.append(self.NEWSLETTER_LFI)
        if not value and self.subscribed:
            self.newsletters.remove(self.NEWSLETTER_LFI)

    def get_full_name(self):
        """
        Returns the first_name plus the last_name, with a space in between.
        """
        full_name = "%s %s" % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        "Returns the short name for the user."
        return self.first_name or self.email

    def get_greeting(self):
        if self.gender == self.GENDER_FEMALE:
            cher = "Chère"
        elif self.gender == self.GENDER_MALE:
            cher = "Cher"
        else:
            cher = "Chèr⋅e"

        if self.first_name and self.last_name:
            machin = self.get_full_name()
        elif self.gender == self.GENDER_FEMALE:
            machin = "insoumise"
        elif self.gender == self.GENDER_MALE:
            machin = "insoumis"
        else:
            machin = "insoumis⋅e"

        return f"{cher} {machin}"

    def add_email(self, email_address, primary=False, **kwargs):
        try:
            email = self.emails.get_by_natural_key(email_address)
        except PersonEmail.DoesNotExist:
            email = PersonEmail.objects.create_email(address=email_address,
                                                     person=self,
                                                     **kwargs)
        else:
            if email.person != self:
                raise IntegrityError(
                    f"L'email '{email_address}' est déjà associé à une autre personne"
                )

            email.bounced = kwargs.get("bounced", email.bounced) or False
            email.bounced_date = kwargs.get("bounced_date", email.bounced_date)
            email.save()

        if primary and email.person == self:
            self.set_primary_email(email)

        return email

    def set_primary_email(self, email_address):
        if isinstance(email_address, PersonEmail):
            email_instance = email_address
        else:
            email_instance = self.emails.get_by_natural_key(email_address)
        order = list(self.get_personemail_order())
        order.remove(email_instance.id)
        order.insert(0, email_instance.id)
        self.set_personemail_order(order)
        self.__dict__["primary_email"] = email_instance

    def get_subscriber_status(self):
        if self.bounced:
            return AbstractSubscriber.STATUS_BOUNCED
        return AbstractSubscriber.STATUS_SUBSCRIBED

    def get_subscriber_email(self):
        return self.email

    def get_subscriber_data(self):
        data = super().get_subscriber_data()

        return {
            **data,
            "login_query": urlencode(generate_token_params(self)),
            "greeting": self.get_greeting(),
            "full_name": self.get_full_name(),
            "short_name": self.get_short_name(),
            "ancienne_region": self.ancienne_region,
            "region": self.region,
            "departement": self.departement,
            "city": self.location_city,
            "short_address": self.short_address,
            "short_location": self.short_location(),
            "full_address": self.html_full_address(),
        }

    def ensure_role_exists(self):
        """Crée un compte pour cette personne si aucun n'existe.

        Cette méthode n'a aucun effet si un compte existe déjà.
        """
        if self.role_id is not None:
            return

        with transaction.atomic():
            self.role = Role.objects.create(
                is_active=True,
                is_staff=False,
                is_superuser=False,
                type=Role.PERSON_ROLE,
            )
            self.save(update_fields=["role"])

    def has_perm(self, perm, obj=None):
        """Simple raccourci pour vérifier les permissions"""
        return self.role.has_perm(perm, obj)
Ejemplo n.º 26
0
class Usuario(ExportModelOperationsMixin('usuario'),
              SimpleEmailConfirmationUserMixin, CustomAbstractUser,
              TemChaveExterna):
    """Classe de autenticacao do django, ela tem muitos perfis."""

    SME = 0
    PREFEITURA = 1

    TIPOS_EMAIL = ((SME, '@sme.prefeitura.sp.gov.br'),
                   (PREFEITURA, '@prefeitura.sp.gov.br'))
    nome = models.CharField(_('name'), max_length=150)
    email = models.EmailField(_('email address'), unique=True)
    tipo_email = models.PositiveSmallIntegerField(choices=TIPOS_EMAIL,
                                                  null=True,
                                                  blank=True)

    registro_funcional = models.CharField(
        _('RF'),
        max_length=7,
        blank=True,
        null=True,
        unique=True,  # noqa DJ01
        validators=[MinLengthValidator(7)])
    cargo = models.CharField(max_length=50, blank=True)

    # TODO: essew atributow deve pertencer somente a um model Pessoa
    cpf = models.CharField(
        _('CPF'),
        max_length=11,
        blank=True,
        null=True,
        unique=True,  # noqa DJ01
        validators=[MinLengthValidator(11)])
    contatos = models.ManyToManyField('dados_comuns.Contato', blank=True)

    # TODO: esses atributos devem pertencer somente a um model Nutricionista
    super_admin_terceirizadas = models.BooleanField(
        'É Administrador por parte das Terceirizadas?', default=False)  # noqa
    crn_numero = models.CharField('Nutricionista crn',
                                  max_length=160,
                                  blank=True,
                                  null=True)  # noqa DJ01

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []  # type: ignore

    def atualizar_cargo(self):
        cargo = self.cargos.filter(ativo=True).last()
        self.cargo = cargo.nome
        self.save()

    def desativa_cargo(self):
        cargo = self.cargos.last()
        if cargo is not None:
            cargo.finalizar_cargo()

    @property
    def vinculos(self):
        return self.vinculos

    @property
    def vinculo_atual(self):
        if self.vinculos.filter(
                Q(data_inicial=None, data_final=None, ativo=False)
                |  # noqa W504 esperando ativacao
                Q(data_inicial__isnull=False, data_final=None, ativo=True)
        ).exists():
            return self.vinculos.get(
                Q(data_inicial=None, data_final=None, ativo=False)
                |  # noqa W504 esperando ativacao
                Q(data_inicial__isnull=False, data_final=None, ativo=True))
        return None

    @property  # noqa C901
    def tipo_usuario(self):
        tipo_usuario = 'indefinido'
        if self.vinculo_atual:
            tipo_usuario = self.vinculo_atual.content_type.model
            if tipo_usuario == 'codae':
                if self.vinculo_atual.perfil.nome in [COORDENADOR_LOGISTICA]:
                    tipo_usuario = 'coordenador_logistica'
                elif self.vinculo_atual.perfil.nome in [
                        COORDENADOR_GESTAO_ALIMENTACAO_TERCEIRIZADA,
                        ADMINISTRADOR_GESTAO_ALIMENTACAO_TERCEIRIZADA
                ]:
                    tipo_usuario = 'gestao_alimentacao_terceirizada'
                elif self.vinculo_atual.perfil.nome in [
                        COORDENADOR_GESTAO_PRODUTO,
                        ADMINISTRADOR_GESTAO_PRODUTO
                ]:
                    tipo_usuario = 'gestao_produto'
                elif self.vinculo_atual.perfil.nome in [
                        COORDENADOR_SUPERVISAO_NUTRICAO,
                        ADMINISTRADOR_SUPERVISAO_NUTRICAO
                ]:
                    tipo_usuario = 'supervisao_nutricao'
                else:
                    tipo_usuario = 'dieta_especial'
        return tipo_usuario

    @property
    def pode_efetuar_cadastro(self):
        dados_usuario = EOLService.get_informacoes_usuario(
            self.registro_funcional)  # noqa
        diretor_de_escola = False
        for dado in dados_usuario:
            if dado['cargo'] == 'DIRETOR DE ESCOLA':
                diretor_de_escola = True
                break
        vinculo_aguardando_ativacao = self.vinculo_atual.status == Vinculo.STATUS_AGUARDANDO_ATIVACAO
        return diretor_de_escola or vinculo_aguardando_ativacao

    def enviar_email_confirmacao(self):
        self.add_email_if_not_exists(self.email)
        content = {
            'uuid': self.uuid,
            'confirmation_key': self.confirmation_key
        }
        titulo = 'Confirmação de E-mail'
        template = 'email_cadastro_funcionario.html'
        dados_template = {
            'titulo': titulo,
            'link_cadastro': url_configs('CONFIRMAR_EMAIL', content),
            'nome': self.nome
        }
        html = render_to_string(template, dados_template)
        self.email_user(
            subject='Confirme seu e-mail - SIGPAE',
            message='',
            template=template,
            dados_template=dados_template,
            html=html,
        )

    def enviar_email_recuperacao_senha(self):
        token_generator = PasswordResetTokenGenerator()
        token = token_generator.make_token(self)
        content = {'uuid': self.uuid, 'confirmation_key': token}
        titulo = 'Recuperação de senha'
        conteudo = f'Clique neste link para criar uma nova senha no SIGPAE: {url_configs("RECUPERAR_SENHA", content)}'
        template = 'email_conteudo_simples.html'
        dados_template = {'titulo': titulo, 'conteudo': conteudo}
        html = render_to_string(template, dados_template)
        self.email_user(
            subject='Email de recuperação de senha - SIGPAE',
            message='',
            template=template,
            dados_template=dados_template,
            html=html,
        )

    def enviar_email_administrador(self):
        self.add_email_if_not_exists(self.email)
        titulo = '[SIGPAE] Novo cadastro de empresa'
        template = 'email_cadastro_terceirizada.html'
        dados_template = {
            'titulo': titulo,
            'link_cadastro': url_configs('LOGIN_TERCEIRIZADAS', {}),
            'nome': self.nome
        }
        html = render_to_string(template, dados_template)
        self.email_user(subject='[SIGPAE] Novo cadastro de empresa',
                        message='',
                        template=template,
                        dados_template=dados_template,
                        html=html)

    def atualiza_senha(self, senha, token):
        token_generator = PasswordResetTokenGenerator()
        if token_generator.check_token(self, token):
            self.set_password(senha)
            self.save()
            return True
        return False

    def criar_vinculo_administrador(self, instituicao, nome_perfil):
        perfil = Perfil.objects.get(nome=nome_perfil)
        Vinculo.objects.create(instituicao=instituicao,
                               perfil=perfil,
                               usuario=self,
                               ativo=False)

    class Meta:
        ordering = ('-super_admin_terceirizadas', )
Ejemplo n.º 27
0
class Charge(ExportModelOperationsMixin('charge'), models.Model):
    name = models.CharField(max_length=255)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    raw_amount = models.CharField(max_length=1023)
    date = models.DateField()
    from_user = models.ForeignKey(User,
                                  on_delete=models.CASCADE,
                                  related_name="revenues")
    to_users = models.ManyToManyField(User, related_name="expenses")
    category = models.ForeignKey(Category,
                                 null=True,
                                 on_delete=models.SET_NULL)

    def __str__(self):
        return '{}: {}'.format(self.from_user.username, self.name)

    def clean(self):
        flat = self.from_user.profile.flat
        if any(user.profile.flat != flat for user in self.to_users.all()):
            raise ValidationError(
                'You cannot charge user from different flat.')

    def save(self, *args, **kwargs):
        self.amount = float(
            ne.evaluate(self.raw_amount,
                        local_dict={},
                        global_dict={},
                        truediv=True))
        super(Charge, self).save(*args, **kwargs)

    @staticmethod
    def get_revenues(year, month, user):
        return Charge.objects.filter(date__year=year,
                                     date__month=month,
                                     from_user=user)

    @staticmethod
    def get_revenue(id, user):
        return Charge.objects.filter(id=id, from_user=user).first()

    @staticmethod
    def get_expenses(year, month, user):
        return Charge.objects.filter(date__year=year,
                                     date__month=month,
                                     to_users=user)

    @staticmethod
    def get_expense(id, user):
        return Charge.objects.filter(id=id, to_users=user).first()

    @staticmethod
    def get_summary(year, month, user):
        revenues = Charge.get_revenues(year, month, user)
        expenses = Charge.get_expenses(year, month, user)

        summary = {}

        def get_or_add(user_to_get):
            if user_to_get.id not in summary.keys():
                summary[user_to_get.id] = {'user': user_to_get, 'amount': 0}
            return summary[user_to_get.id]

        for revenue in revenues:
            rev_users = revenue.to_users.all()
            for rev_user in rev_users:
                entry = get_or_add(rev_user)
                entry['amount'] -= revenue.amount / len(rev_users)

        for expense in expenses:
            entry = get_or_add(expense.from_user)
            entry['amount'] += expense.amount / expense.to_users.count()

        for user in User.objects.filter(profile__flat=user.profile.flat):
            get_or_add(user)

        return summary
Ejemplo n.º 28
0
class ItemImage(ExportModelOperationsMixin('item_image'), models.Model):
    item = models.ForeignKey(Item,
                             related_name='images',
                             on_delete=models.CASCADE)
    image = models.ImageField(upload_to='%Y/%m/%d')
    thumbnail = models.ImageField(upload_to='%Y/%m/%d')

    def save(self, *args, **kwargs):
        self.correct_image()
        super(ItemImage, self).save(*args, **kwargs)

    def correct_image(self):
        try:
            # Open image and set some variables
            img = Image.open(self.image)
            img_format = img.format
            max_size = (800, 800)
            thumb_size = (150, 150)
            file_ext = os_path.splitext(self.image.name)[1]
            random_str = token_urlsafe(12)
            img_name = f'{self.item.id}-{random_str}.%s{file_ext}'
            img_bytes = BytesIO()
            thumb_bytes = BytesIO()

            # If image contains exif check for orientation and rotate
            for orientation in ExifTags.TAGS.keys():
                if ExifTags.TAGS[orientation] == 'Orientation':
                    img_exif = img._getexif()
                    if img_exif:
                        if orientation in img_exif:
                            image_orientation = img_exif[orientation]
                            if image_orientation == 3:
                                img = img.rotate(180, expand=True)
                            if image_orientation == 6:
                                img = img.rotate(-90, expand=True)
                            if image_orientation == 8:
                                img = img.rotate(90, expand=True)

            # Store a copy of the image for thumbnail
            thumb = img.copy()

            # TODO - task this and just present page without until it's done

            # Correct the image size to safe maximums
            img.thumbnail(max_size, Image.ANTIALIAS)
            img.save(img_bytes, format=img_format, quality=80)
            self.image = InMemoryUploadedFile(img_bytes, 'ImageField',
                                              img_name % 'full',
                                              self.image.file.content_type,
                                              img.size,
                                              self.image.file.charset)

            # Create thumbnail from image
            thumb.thumbnail(thumb_size, Image.ANTIALIAS)
            thumb.save(thumb_bytes, format=img_format, quality=80)
            self.thumbnail = InMemoryUploadedFile(thumb_bytes, 'ImageField',
                                                  img_name % 'thumbnail',
                                                  self.image.file.content_type,
                                                  img.size,
                                                  self.image.file.charset)

            thumb.close()
            img.close()
        except:
            raise Exception('Unable to correct image size')

    def __str__(self):
        return f'{self.id} - {self.item.name} - {self.id}'
Ejemplo n.º 29
0
class Domain(ExportModelOperationsMixin('Domain'), models.Model):
    created = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=191,
                            unique=True,
                            validators=validate_domain_name)
    owner = models.ForeignKey(User,
                              on_delete=models.PROTECT,
                              related_name='domains')
    published = models.DateTimeField(null=True, blank=True)
    minimum_ttl = models.PositiveIntegerField(default=get_minimum_ttl_default)
    _keys = None

    @classmethod
    def is_registrable(cls, domain_name: str, user: User):
        """
        Returns False in any of the following cases:
        (a) the domain name is under .internal,
        (b) the domain_name appears on the public suffix list,
        (c) the domain is descendant to a zone that belongs to any user different from the given one,
            unless it's parent is a public suffix, either through the Internet PSL or local settings.
        Otherwise, True is returned.
        """
        if domain_name != domain_name.lower():
            raise ValueError

        if f'.{domain_name}'.endswith('.internal'):
            return False

        try:
            public_suffix = psl.get_public_suffix(domain_name)
            is_public_suffix = psl.is_public_suffix(domain_name)
        except (Timeout, NoNameservers):
            public_suffix = domain_name.rpartition('.')[2]
            is_public_suffix = ('.'
                                not in domain_name)  # TLDs are public suffixes
        except psl_dns.exceptions.UnsupportedRule as e:
            # It would probably be fine to treat this as a non-public suffix (with the TLD acting as the
            # public suffix and setting both public_suffix and is_public_suffix accordingly).
            # However, in order to allow to investigate the situation, it's better not catch
            # this exception. For web requests, our error handler turns it into a 503 error
            # and makes sure admins are notified.
            raise e

        if not is_public_suffix:
            # Take into account that any of the parent domains could be a local public suffix. To that
            # end, identify the longest local public suffix that is actually a suffix of domain_name.
            # Then, override the global PSL result.
            for local_public_suffix in settings.LOCAL_PUBLIC_SUFFIXES:
                has_local_public_suffix_parent = (
                    '.' + domain_name).endswith('.' + local_public_suffix)
                if has_local_public_suffix_parent and len(
                        local_public_suffix) > len(public_suffix):
                    public_suffix = local_public_suffix
                    is_public_suffix = (public_suffix == domain_name)

        if is_public_suffix and domain_name not in settings.LOCAL_PUBLIC_SUFFIXES:
            return False

        # Generate a list of all domains connecting this one and its public suffix.
        # If another user owns a zone with one of these names, then the requested
        # domain is unavailable because it is part of the other user's zone.
        private_components = domain_name.rsplit(public_suffix,
                                                1)[0].rstrip('.')
        private_components = private_components.split(
            '.') if private_components else []
        private_components += [public_suffix]
        private_domains = [
            '.'.join(private_components[i:])
            for i in range(0,
                           len(private_components) - 1)
        ]
        assert is_public_suffix or domain_name == private_domains[0]

        # Deny registration for non-local public suffixes and for domains covered by other users' zones
        user = user if not isinstance(user, AnonymousUser) else None
        return not cls.objects.filter(
            Q(name__in=private_domains) & ~Q(owner=user)).exists()

    @property
    def keys(self):
        if not self._keys:
            self._keys = pdns.get_keys(self)
        return self._keys

    @property
    def is_locally_registrable(self):
        return self.parent_domain_name in settings.LOCAL_PUBLIC_SUFFIXES

    @property
    def parent_domain_name(self):
        return self._partitioned_name[1]

    @property
    def _partitioned_name(self):
        subname, _, parent_name = self.name.partition('.')
        return subname, parent_name or None

    def save(self, *args, **kwargs):
        self.full_clean(validate_unique=False)
        super().save(*args, **kwargs)

    def update_delegation(self, child_domain: Domain):
        child_subname, child_domain_name = child_domain._partitioned_name
        if self.name != child_domain_name:
            raise ValueError(
                'Cannot update delegation of %s as it is not an immediate child domain of %s.'
                % (child_domain.name, self.name))

        if child_domain.pk:
            # Domain real: set delegation
            child_keys = child_domain.keys
            if not child_keys:
                raise APIException(
                    'Cannot delegate %s, as it currently has no keys.' %
                    child_domain.name)

            RRset.objects.create(domain=self,
                                 subname=child_subname,
                                 type='NS',
                                 ttl=3600,
                                 contents=settings.DEFAULT_NS)
            RRset.objects.create(
                domain=self,
                subname=child_subname,
                type='DS',
                ttl=300,
                contents=[ds for k in child_keys for ds in k['ds']])
            metrics.get('desecapi_autodelegation_created').inc()
        else:
            # Domain not real: remove delegation
            for rrset in self.rrset_set.filter(subname=child_subname,
                                               type__in=['NS', 'DS']):
                rrset.delete()
            metrics.get('desecapi_autodelegation_deleted').inc()

    def delete(self):
        ret = super().delete()
        logger.warning(f'Domain {self.name} deleted (owner: {self.owner.pk})')
        return ret

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('created', )
Ejemplo n.º 30
0
class InclusaoAlimentacaoContinua(
        ExportModelOperationsMixin('inclusao_continua'), IntervaloDeDia,
        Descritivel, TemChaveExterna, DiasSemana,
        FluxoAprovacaoPartindoDaEscola, CriadoPor,
        TemIdentificadorExternoAmigavel, CriadoEm, Logs, TemPrioridade,
        SolicitacaoForaDoPrazo, TemTerceirizadaConferiuGestaoAlimentacao):
    # TODO: noralizar campo de Descritivel: descricao -> observacao
    DESCRICAO = 'Inclusão de Alimentação Contínua'

    outro_motivo = models.CharField('Outro motivo', blank=True, max_length=500)
    motivo = models.ForeignKey(MotivoInclusaoContinua,
                               on_delete=models.DO_NOTHING)
    escola = models.ForeignKey('escola.Escola',
                               on_delete=models.DO_NOTHING,
                               related_name='inclusoes_alimentacao_continua')
    observacao = models.CharField('Observação', blank=True, max_length=1000)
    objects = models.Manager()  # Manager Padrão
    desta_semana = InclusoesDeAlimentacaoContinuaDestaSemanaManager()
    deste_mes = InclusoesDeAlimentacaoContinuaDesteMesManager()
    vencidos = InclusoesDeAlimentacaoContinuaVencidaDiasManager()

    @property
    def data(self):
        data = self.data_inicial
        if self.data_final < data:
            data = self.data_final
        return data

    @classmethod
    def get_solicitacoes_rascunho(cls, usuario):
        inclusoes_continuas = cls.objects.filter(
            criado_por=usuario,
            status=InclusaoAlimentacaoContinua.workflow_class.RASCUNHO)
        return inclusoes_continuas

    @property
    def quantidades_periodo(self):
        return self.quantidades_por_periodo

    @property
    def template_mensagem(self):
        template = TemplateMensagem.objects.get(
            tipo=TemplateMensagem.INCLUSAO_ALIMENTACAO_CONTINUA)
        template_troca = {
            '@id': self.id_externo,
            '@criado_em': str(self.criado_em),
            '@criado_por': str(self.criado_por),
            '@status': str(self.status),
            # TODO: verificar a url padrão do pedido
            '@link': 'http://teste.com',
        }
        corpo = template.template_html
        for chave, valor in template_troca.items():
            corpo = corpo.replace(chave, valor)
        return template.assunto, corpo

    def salvar_log_transicao(self, status_evento, usuario, **kwargs):
        justificativa = kwargs.get('justificativa', '')
        resposta_sim_nao = kwargs.get('resposta_sim_nao', False)
        LogSolicitacoesUsuario.objects.create(
            descricao=str(self),
            status_evento=status_evento,
            solicitacao_tipo=LogSolicitacoesUsuario.
            INCLUSAO_ALIMENTACAO_CONTINUA,
            usuario=usuario,
            uuid_original=self.uuid,
            justificativa=justificativa,
            resposta_sim_nao=resposta_sim_nao)

    def __str__(self):
        return f'de {self.data_inicial} até {self.data_final} para {self.escola} para {self.dias_semana_display()}'

    class Meta:
        verbose_name = 'Inclusão de alimentação contínua'
        verbose_name_plural = 'Inclusões de alimentação contínua'
        ordering = ['data_inicial']