Exemplo n.º 1
0
class Contact(ContactModel):
    @ChoiceEnum.labels(str.capitalize)
    class Type(ChoiceEnum):
        manager = 0
        contact = 1
        ugettext_noop("Manager")
        ugettext_noop("Contact")

    center = models.ForeignKey(
        Center, related_name='contacts', on_delete=models.CASCADE
    )
    type = EnumField(Type, db_index=True)

    def __str__(self):
        return "{} ({})".format(self.get_full_name(), Contact.Type(self.type))
Exemplo n.º 2
0
class EventWish(models.Model):
    applicant = models.ForeignKey(Applicant, on_delete=models.CASCADE)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    status = EnumField(
        ApplicantStatusTypes,
        db_index=True,
        blank=True,
        default=ApplicantStatusTypes.incomplete.value,
    )

    # Priority defined by the candidate to express his preferred event
    # The lower the order is, the more important is the choice
    order = models.IntegerField(default=1)

    def __str__(self):
        return '{} for {}'.format(str(self.applicant), str(self.event))

    class Meta:
        ordering = ('order', )
        unique_together = (('applicant', 'event'), )
Exemplo n.º 3
0
class Question(models.Model):
    """
    A generic question type, that can be of several type.

    If response_type is multichoice you have to specify the answer in the meta
    field, respecting the following structure:
    {
        "choices": {
            "0": "first option",
            "1": "second option"
        }
    }
    """

    # Formulation of the question
    question = models.TextField()
    # Potential additional indications about the questions
    comment = models.TextField(blank=True)
    # How to represent the answer
    response_type = EnumField(AnswerTypes)

    # If set to true, the applicant will need to fill this field in order to
    # save his application.
    always_required = models.BooleanField(default=False)
    # If set to true, the applicant will need to fill this field in order to
    # validate his application.
    finaly_required = models.BooleanField(default=True)

    # Some extra constraints on the answer
    meta = JSONField(encoder=DjangoJSONEncoder, default=dict, null=True)

    def __str__(self):
        ret = self.question

        if self.finaly_required:
            ret += ' (*)'

        return ret
Exemplo n.º 4
0
class Center(AddressableModel):
    @ChoiceEnum.labels(str.capitalize)
    class Type(ChoiceEnum):
        center = 0
        restaurant = 1
        hotel = 2
        pizzeria = 3
        other = 4
        ugettext_noop("Center")
        ugettext_noop("Restaurant")
        ugettext_noop("Hotel")
        ugettext_noop("Pizzeria")
        ugettext_noop("Other")

    name = models.CharField(max_length=64)
    type = EnumField(Type)
    is_active = models.BooleanField(default=True)

    lat = models.DecimalField(default=0, max_digits=16, decimal_places=6)
    lng = models.DecimalField(default=0, max_digits=16, decimal_places=6)

    comments = models.TextField(blank=True)

    objects = CenterQuerySet.as_manager()

    class Meta:
        ordering = ('type', 'name', 'city')

    @property
    def coordinates(self):
        return "{:.6f};{:.6f}".format(self.lat, self.lng)

    @property
    def has_valid_geolocation(self):
        return self.lat != 0 and self.lng != 0

    def geocode(self, suffix=', FRANCE', geocoder=None):
        if geocoder is None:
            geocoder = geopy.geocoders.get_geocoder_for_service('google')
        location = geocoder().geocode(
            "{name}, {addr}, {code} {city} {suffix}".format(
                name=self.name,
                addr=self.address,
                code=self.postal_code,
                city=self.city,
                suffix=suffix,
            ),
            language='fr',
            timeout=10,
        )
        self.lat = location.latitude
        self.lng = location.longitude
        self.save()

    def normalize(self, suffix=', FRANCE', geocoder=None):
        if geocoder is None:
            geocoder = geopy.geocoders.get_geocoder_for_service('google')
        location = geocoder().geocode(
            "{addr}, {code} {city} {suffix}".format(
                addr=self.address,
                code=self.postal_code,
                city=self.city,
                suffix=suffix,
            ),
            language='fr',
            timeout=10,
        )
        addr, city, country = location.address.split(',')
        if country.strip().lower() != 'france':
            raise ValueError("Country is not France")
        code, city = city.split(None, 1)
        self.address = addr.strip()
        self.postal_code = code.strip()
        self.city = city.strip()
        self.save()

    def __str__(self):
        return self.name
Exemplo n.º 5
0
class Event(ExportModelOperationsMixin('event'), models.Model):
    @register_enum(namespace='Event')
    @ChoiceEnum.labels(lambda e: "Regional event"
                       if e == "semifinal" else e.capitalize())
    class Type(ChoiceEnum):
        qualification = 0
        semifinal = 1
        final = 2
        ugettext_noop("Qualification")
        ugettext_noop("Regional event")
        ugettext_noop("Final")

    edition = models.ForeignKey(Edition,
                                related_name='events',
                                on_delete=models.CASCADE)
    center = models.ForeignKey(Center,
                               blank=True,
                               null=True,
                               related_name='events',
                               on_delete=models.SET_NULL)
    type = EnumField(Type, db_index=True)
    date_begin = models.DateTimeField(blank=True, null=True)
    date_end = models.DateTimeField(blank=True, null=True)

    objects = EventManager()

    class Meta:
        ordering = ('date_begin', )

    @classmethod
    def semifinals_for_edition(cls, year: int):
        return cls.objects.select_related('center').filter(
            edition__year=year, type=cls.Type.semifinal.value)

    @classmethod
    def final_for_edition(cls, year: int):
        return cls.objects.select_related('center').get(
            edition__year=year, type=cls.Type.final.value)

    @property
    def is_finished(self):
        return self.date_end < timezone.now()

    @property
    def is_in_future(self):
        return timezone.now() < self.date_begin

    @property
    def is_active(self):
        if self.date_begin is None or self.date_end is None:
            return False
        return self.date_begin <= timezone.now() <= self.date_end

    @property
    def challenge(self):
        from problems.models import Challenge  # circular import
        try:
            return Challenge.by_year_and_event_type(self.edition.year,
                                                    Event.Type(self.type))
        except ObjectDoesNotExist:
            return None

    @property
    def short_description(self):
        return "{} – {}".format(
            self.center.name, date_format(self.date_begin,
                                          "SHORT_DATE_FORMAT"))

    def __str__(self):
        return "{edition}: {type} starting {starting}{at}".format(
            edition=self.edition,
            type=self.get_type_display(),
            starting=self.date_begin,
            at=" at %s" % self.center if self.center else "",
        )
Exemplo n.º 6
0
class ContestantCorrection(ExportModelOperationsMixin('contestant_correction'),
                           models.Model):
    contestant = models.ForeignKey(Contestant,
                                   related_name='corrections',
                                   on_delete=models.CASCADE)
    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               related_name='contestant_correction',
                               null=True,
                               blank=True,
                               on_delete=models.SET_NULL)
    comment = models.TextField(blank=True)
    event_type = EnumField(Event.Type, db_index=True)
    changes = JSONField(blank=True)
    date_created = models.DateTimeField(default=timezone.now, db_index=True)

    class Meta:
        get_latest_by = 'date_created'
        ordering = ('-' + get_latest_by, )

    @property
    def date_created_utc(self):
        return int(
            timezone.make_naive(self.date_created, timezone.utc).timestamp())

    def get_changes(self, precision='0.01'):
        precision = Decimal(precision)
        event_type = Event.Type(self.event_type)

        def get_event_description(value):
            return Event.objects.select_related('center').get(
                pk=value).short_description

        def build():
            for field in self.contestant.get_score_fields_for_type(event_type):
                try:
                    value = self.changes[field.name]
                except KeyError:
                    continue
                yield {
                    'field':
                    field,
                    'type':
                    'score',
                    'value':
                    None
                    if value is None else Decimal(value).quantize(precision),
                }

            for enum in ('assignation_semifinal', 'assignation_final'):
                try:
                    value = self.changes[enum]
                except KeyError:
                    continue

                yield {
                    'field': Contestant._meta.get_field(enum),
                    'type': 'enum',
                    'value': Assignation.label_for(Assignation(value)),
                }

            for nullable, getter in (('assignation_semifinal_event',
                                      get_event_description), ):
                try:
                    value = self.changes[nullable]
                except KeyError:
                    continue
                if value is not None:
                    try:
                        value = getter(value)
                    except Exception:
                        continue
                yield {
                    'field': Contestant._meta.get_field(nullable),
                    'type': 'nullable',
                    'value': value,
                }

        return list(build())
Exemplo n.º 7
0
class Contestant(ExportModelOperationsMixin('contestant'), models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             related_name='contestants',
                             on_delete=models.CASCADE)
    edition = models.ForeignKey(Edition,
                                related_name='contestants',
                                on_delete=models.CASCADE)

    school = models.ForeignKey(School,
                               related_name='contestants',
                               null=True,
                               blank=True,
                               default=None,
                               on_delete=models.SET_NULL)

    shirt_size = EnumField(
        ShirtSize,
        null=True,
        blank=True,
        db_index=True,
        verbose_name=_("T-shirt size"),
        empty_label=_("Choose your size"),
        help_text=_(
            "We usually provide unisex Prologin t-shirts to the finalists."))
    preferred_language = CodingLanguageField(
        blank=True,
        db_index=True,
        help_text=_(
            "The programming language you will most likely use during the "
            "regional events."))
    learn_about_contest = EnumField(
        LearnAboutContest,
        null=True,
        blank=True,
        db_index=True,
        verbose_name=_("How did you learn about the contest ?"),
        empty_label=_("Tell us how you discovered the contest"))
    assignation_semifinal = EnumField(
        Assignation,
        default=Assignation.not_assigned.value,
        verbose_name=_("Regional event assignation status"))
    assignation_semifinal_wishes = models.ManyToManyField(
        Event,
        through='EventWish',
        related_name='applicants',
        blank=True,
        verbose_name=_("Regional event assignation whishes"))
    assignation_semifinal_event = models.ForeignKey(
        Event,
        related_name='assigned_contestants',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_("Regional event assigned event"))
    assignation_final = EnumField(Assignation,
                                  default=Assignation.not_assigned.value,
                                  verbose_name=_("Final assignation status"))

    score_qualif_qcm = models.IntegerField(blank=True,
                                           null=True,
                                           verbose_name=_("Quiz score"))
    score_qualif_algo = models.IntegerField(
        blank=True, null=True, verbose_name=_("Algo exercises score"))
    score_qualif_bonus = models.IntegerField(blank=True,
                                             null=True,
                                             verbose_name=_("Bonus score"))
    score_semifinal_written = models.IntegerField(
        blank=True, null=True, verbose_name=_("Written exam score"))
    score_semifinal_interview = models.IntegerField(
        blank=True, null=True, verbose_name=_("Interview score"))
    score_semifinal_machine = models.IntegerField(
        blank=True, null=True, verbose_name=_("Machine exam score"))
    score_semifinal_bonus = models.IntegerField(blank=True,
                                                null=True,
                                                verbose_name=_("Bonus score"))
    score_final = models.IntegerField(blank=True,
                                      null=True,
                                      verbose_name=_("Score"))
    score_final_bonus = models.IntegerField(blank=True,
                                            null=True,
                                            verbose_name=_("Bonus score"))
    is_home_public = models.BooleanField(default=False)

    objects = ContestantManager()
    complete_for_semifinal = ContestantCompleteSemifinalManager()

    class Meta:
        unique_together = ('user', 'edition')

    def get_advancement_enum(self):
        if self.is_assigned_for_final:
            return Event.Type.final
        if self.is_assigned_for_semifinal:
            return Event.Type.semifinal
        return Event.Type.qualification

    def get_score_fields_for_type(self, type: Event.Type) -> dict:
        mapping = {Event.Type.qualification: 'qualif'}
        return collections.OrderedDict([
            (field, getattr(self, field.name))
            for field in self._meta.get_fields()
            if field.name.startswith('score_{}_'.format(
                mapping.get(type, type.name)))
        ])

    @cached_property
    def has_mandatory_info(self):
        # update ContestantManager.complete_for_{,semi}final accordingly
        return all(
            (self.shirt_size
             is not None, self.preferred_language, self.user.first_name,
             self.user.last_name, self.user.address, self.user.postal_code,
             self.user.city, self.user.country, self.user.birthday))

    @property
    def _wish_count(self):
        try:
            return self.wish_count
        except AttributeError:
            self.wish_count = (self.assignation_semifinal_wishes.filter(
                type=Event.Type.semifinal.value).distinct().count())
            return self.wish_count

    @property
    def home_path(self):
        return os.path.join(settings.HOMES_PATH, str(self.edition.year),
                            '{}.{}'.format(self.user.pk, 'tar.gz'))

    @property
    def has_home(self):
        return os.path.exists(self.home_path)

    @property
    def home_filename(self):
        return 'home-final-{year}-{username}.tar.gz'.format(
            year=self.edition.year, username=self.user.normalized_username)

    @property
    def home_size(self):
        try:
            return os.path.getsize(self.home_path)
        except OSError:
            return 0

    @cached_property
    def qualification_problems_completion(self):
        import problems.models
        challenge = problems.models.Challenge.by_year_and_event_type(
            self.edition.year, Event.Type.qualification)
        problem_count = len(challenge.problems)
        qualif_problem_answers = problems.models.Submission.objects.filter(
            user=self.user, challenge=challenge.name)
        completed_problems = qualif_problem_answers.count()
        return 0 if completed_problems == 0 else 2 if completed_problems == problem_count else 1

    @cached_property
    def qualification_qcm_completion(self):
        import qcm.models
        current_qcm = (qcm.models.Qcm.objects.filter(
            event__edition=self.edition,
            event__type=Event.Type.qualification.value).first())
        if not current_qcm:
            return 2
        question_count = current_qcm.question_count
        completed_questions = current_qcm.completed_question_count_for(self)
        return 0 if completed_questions == 0 else 2 if completed_questions == question_count else 1

    @cached_property
    def qualification_completion(self):
        if not self.is_complete_for_semifinal:
            return 0
        return min(self.qualification_problems_completion,
                   self.qualification_qcm_completion)

    @cached_property
    def is_young_enough(self):
        return self.user.young_enough_to_compete(self.edition.year)

    @cached_property
    def has_enough_semifinal_wishes(self):
        return self._wish_count >= settings.PROLOGIN_SEMIFINAL_MIN_WISH_COUNT

    @cached_property
    def is_complete_for_semifinal(self):
        # update ContestantManager.complete_for_semifinal accordingly
        return (self.has_mandatory_info and self.is_young_enough
                and self.has_enough_semifinal_wishes)

    @property
    def is_assigned_for_semifinal(self):
        return self.assignation_semifinal == Assignation.assigned.value

    @property
    def has_abandoned_semifinal(self):
        return self.assignation_semifinal == Assignation.abandoned.value

    @property
    def is_ruled_out_for_semifinal(self):
        return self.assignation_semifinal == Assignation.ruled_out.value

    @property
    def is_assigned_for_final(self):
        return self.assignation_final == Assignation.assigned.value

    @property
    def is_ruled_out_for_final(self):
        return self.assignation_final == Assignation.ruled_out.value

    @property
    def completed_qualification(self):
        return self.is_assigned_for_semifinal or self.is_ruled_out_for_semifinal

    @property
    def completed_semifinal(self):
        return self.is_assigned_for_final or self.is_ruled_out_for_final

    def score_for(self, event_type: Event.Type):
        return sum(
            score or 0
            for score in self.get_score_fields_for_type(event_type).values())

    @property
    def score_for_qualification(self):
        return self.score_for(Event.Type.qualification)

    @property
    def score_for_semifinal(self):
        return self.score_for(Event.Type.semifinal)

    @property
    def assignation_final_event(self):
        return Event.objects.get(edition=self.edition,
                                 type=Event.Type.final.value)

    @property
    def total_score(self):
        return sum(
            getattr(self, field.name) or 0
            for field in self._meta.get_fields()
            if field.name.startswith('score_'))

    @cached_property
    def semifinal_challenge(self):
        from problems.models import Challenge
        return Challenge.by_year_and_event_type(self.edition.year,
                                                Event.Type.semifinal)

    @cached_property
    def semifinal_explicitly_unlocked_problems(self):
        from problems.models import ExplicitProblemUnlock
        return ExplicitProblemUnlock.objects.filter(
            challenge=self.semifinal_challenge.name, user=self.user)

    @cached_property
    def semifinal_lines_of_code(self):
        from problems.models import SubmissionCode
        return sum(1 for code in (SubmissionCode.objects.filter(
            submission__user=self.user,
            submission__challenge=self.semifinal_challenge.name).values_list(
                'code', flat=True))
                   for line in code.replace('\r', '\n').split('\n')
                   if line.strip())

    @cached_property
    def semifinal_problems_score(self):
        from problems.models import Submission
        return (Submission.objects.filter(
            user=self.user, challenge=self.semifinal_challenge.name).aggregate(
                score=Sum(Submission.ScoreFunc))['score'])

    @cached_property
    def available_semifinal_problems(self):
        from problems.models import Challenge, Submission, ExplicitProblemUnlock
        challenge = self.semifinal_challenge

        if self.user.is_staff:
            return list(challenge.problems)

        elif challenge.type == Challenge.Type.standard:
            return list(challenge.problems)

        seed = self.user.pk
        problem_to_solved_date = {}
        problem_to_unlock_date = {}
        difficulty_to_solved = collections.defaultdict(set)

        for unlock in ExplicitProblemUnlock.objects.filter(
                user=self.user, challenge=challenge.name):
            problem = challenge.problem(unlock.problem)
            problem_to_unlock_date[problem] = unlock.date_created

        for submission in Submission.objects.filter(user=self.user,
                                                    challenge=challenge.name,
                                                    score_base__gt=0):
            problem = challenge.problem(submission.problem)
            problem_to_solved_date[problem] = submission.first_code_success(
            ).date_submitted
            difficulty_to_solved[problem.difficulty].add(problem)

        # transform into real dict, so we don't get an empty set but an actual KeyError on missing keys
        difficulty_to_solved = dict(difficulty_to_solved)

        dl = challenge.problem_difficulty_list
        for previous_difficulty, difficulty in itertools.zip_longest(
                dl, dl[1:]):
            problems = challenge.problems_of_difficulty(difficulty)

            try:
                earliest_unlock_date = min(
                    problem_to_solved_date[problem]
                    for problem in difficulty_to_solved[previous_difficulty])
            except KeyError:
                # no solved problems of previous_difficulty
                earliest_unlock_date = None

            if challenge.type == Challenge.Type.all_per_level:
                if earliest_unlock_date:
                    for problem in problems:
                        problem_to_unlock_date[problem] = earliest_unlock_date

            elif challenge.type == Challenge.Type.one_per_level:
                if earliest_unlock_date:
                    with save_random_state(seed):
                        problem_to_unlock_date[random.choice(
                            problems)] = earliest_unlock_date

            elif challenge.type == Challenge.Type.one_per_level_delayed:
                # number of solved problems of previous_difficulty
                quantity = len(
                    difficulty_to_solved.get(previous_difficulty, []))
                # unlock (number of solved previous_difficulty) problems of difficulty
                quantity = min(quantity, len(problems))
                if quantity:
                    with save_random_state(seed):
                        for problem in random.sample(problems, quantity):
                            problem_to_unlock_date[
                                problem] = earliest_unlock_date
                # unlock a single previous-difficulty problem if still no success at this difficulty
                if earliest_unlock_date and not difficulty_to_solved.get(
                        difficulty):
                    # previous-difficulty problems that are not already unlocked
                    previous_difficulty_problems = [
                        problem for problem in
                        challenge.problems_of_difficulty(previous_difficulty)
                        if problem not in problem_to_unlock_date
                    ]
                    if previous_difficulty_problems:
                        if (timezone.now() - earliest_unlock_date
                            ).seconds > challenge.auto_unlock_delay:
                            # waited long enough, unlock a new previous-difficulty problem
                            with save_random_state(seed):
                                problem = random.choice(
                                    previous_difficulty_problems)
                                problem_to_unlock_date[
                                    problem] = earliest_unlock_date

        # should not happen, but you never know (eg. if explicit unlock is removed after it's solved)
        # assign a fake unlock date to problems that are solved but not unlocked (wtf)
        for problem, solved_date in problem_to_solved_date.items():
            if problem not in problem_to_unlock_date:
                problem_to_unlock_date[problem] = solved_date

        return {
            problem: {
                'unlocked': date_unlocked,
                'solved': problem_to_solved_date.get(problem)
            }
            for problem, date_unlocked in problem_to_unlock_date.items()
        }

    def compute_changes(self, new, event_type):
        changes = {
            field.name: getattr(new, field.name)
            for field, value in new.get_score_fields_for_type(
                event_type).items() if getattr(self, field.name) != value
        }
        if event_type == Event.Type.qualification:
            if self.assignation_semifinal != new.assignation_semifinal:
                changes['assignation_semifinal'] = new.assignation_semifinal
            if self.assignation_semifinal_event != new.assignation_semifinal_event:
                changes['assignation_semifinal_event'] = (
                    new.assignation_semifinal_event.pk
                    if new.assignation_semifinal_event else None)
        elif event_type == Event.Type.semifinal:
            if self.assignation_final != new.assignation_final:
                changes['assignation_final'] = new.assignation_final
        return changes

    def __str__(self):
        return "{edition}: {user}".format(user=self.user, edition=self.edition)
Exemplo n.º 8
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
Exemplo n.º 9
0
class GCCUser(AbstractUser, AddressableModel):
    @staticmethod
    def upload_seed(instance):
        return 'prologinuser/{}'.format(instance.pk).encode()

    # user have to be imported by the oauth client
    id = models.IntegerField(primary_key=True)

    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 Girls Can Code! 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"))

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

    @cached_property
    def participations_count(self):
        applicants = Applicant.objects.filter(user=self)
        return sum((applicant.status == ApplicantStatusTypes.confirmed.value)
                   for applicant in applicants)

    @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.first_name,
            self.last_name,
            self.email,
            self.gender,
            self.birthday,
            self.phone,
        ))

    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,
        )
Exemplo n.º 10
0
class Center(AddressableModel):
    @register_enum(namespace='Center')
    @ChoiceEnum.labels(str.capitalize)
    class Type(ChoiceEnum):
        center = 0
        restaurant = 1
        hotel = 2
        pizzeria = 3
        other = 4
        ugettext_noop("Center")
        ugettext_noop("Restaurant")
        ugettext_noop("Hotel")
        ugettext_noop("Pizzeria")
        ugettext_noop("Other")

    name = models.CharField(max_length=64)
    type = EnumField(Type)
    is_active = models.BooleanField(default=True)

    lat = models.DecimalField(default=0, max_digits=16, decimal_places=6)
    lng = models.DecimalField(default=0, max_digits=16, decimal_places=6)

    comments = models.TextField(blank=True)

    class Meta:
        ordering = (
            'type',
            'name',
            'city',
        )

    @property
    def coordinates(self):
        return "{:.6f};{:.6f}".format(self.lat, self.lng)

    @property
    def has_valid_geolocation(self):
        return self.lat != 0 and self.lng != 0

    def get_osm_url(self):
        return ('https://www.openstreetmap.org/'
                f'?mlat={self.lat:.6f}&mlon={self.lng:.6f}'
                f'#map=16/{self.lat:.6f}/{self.lng:.6f}')

    def get_absolute_url(self):
        return reverse("centers:detailMap", kwargs={'id': self.id})

    def geocode(self, suffix=', FRANCE', geocoder=None):
        if geocoder is None:
            geocoder = geopy.geocoders.get_geocoder_for_service('google')
        location = geocoder().geocode(
            "{name}, {addr}, {code} {city} {suffix}".format(
                name=self.name,
                addr=self.address,
                code=self.postal_code,
                city=self.city,
                suffix=suffix,
            ),
            language='fr',
            timeout=10)
        self.lat = location.latitude
        self.lng = location.longitude
        self.save()

    def normalize(self, suffix=', FRANCE', geocoder=None):
        if geocoder is None:
            geocoder = geopy.geocoders.get_geocoder_for_service('google')
        location = geocoder().geocode("{addr}, {code} {city} {suffix}".format(
            addr=self.address,
            code=self.postal_code,
            city=self.city,
            suffix=suffix,
        ),
                                      language='fr',
                                      timeout=10)
        addr, city, country = location.address.split(',')
        if country.strip().lower() != 'france':
            raise ValueError("Country is not France")
        code, city = city.split(None, 1)
        self.address = addr.strip()
        self.postal_code = code.strip()
        self.city = city.strip()
        self.save()

    def __str__(self):
        return self.name