class Question(models.Model): """FAQ Questions.""" question = TranslatedField( models.CharField( max_length=200, verbose_name=_("str_question")), {settings.LANGUAGES[0][0]: {"blank": False}}, attrgetter=fallback_to_default,) answer = TranslatedField( HTMLField( verbose_name=_("str_answer")), {settings.LANGUAGES[0][0]: {"blank": False}}, attrgetter=fallback_to_default,) topic = models.ForeignKey(Topic, related_name='question', on_delete=models.CASCADE) number = models.PositiveIntegerField() class Meta: unique_together = ("number", "topic") verbose_name = _("str_Question") verbose_name_plural = _("str_Questions") ordering = ['number'] def __unicode__(self): return u'(%s) %s' % (self.number, self.question, ) def __str__(self): return self.question
class ListDisplayModel(models.Model): name = TranslatedField(models.CharField(_("name"), max_length=200)) choice = TranslatedField( models.CharField(_("choice"), max_length=3, choices=[("a", "Andrew"), ("b", "Betty")])) ordering = models.IntegerField(_("ordering"), default=0)
class Question(models.Model): question = TranslatedField( models.CharField(verbose_name="Question Verbose Name", max_length=200), ) answer = TranslatedField( models.CharField(verbose_name="Answer Verbose Name", max_length=200), ) def __str__(self): return self.question
class Question(models.Model): question = TranslatedField( models.CharField(_("soru"), max_length=200), ) answer = TranslatedField( models.CharField(_("cevap"), max_length=200), ) def __str__(self): return self.question
class TestModel(models.Model): name = TranslatedField(models.CharField(_("name"), max_length=200)) other = TranslatedField( models.CharField(_("other field"), max_length=200, blank=True)) def __str__(self): return self.name stuff_en = "eng" stuff_de = "ger"
class ListDisplayModel(models.Model): name = TranslatedField(models.CharField(_("name"), max_length=200)) choice = TranslatedField( models.CharField(_("choice"), max_length=3, choices=[("a", "Andrew"), ("b", "Betty")])) is_active = TranslatedField( models.BooleanField(_("is active"), default=True)) file = TranslatedField(models.FileField(_("file"), blank=True)) ordering = models.IntegerField(_("ordering"), default=0)
class TestModel(models.Model): name = TranslatedField( models.CharField(_('name'), max_length=200), ) other = TranslatedField( models.CharField(_('other field'), max_length=200, blank=True), ) def __str__(self): return self.name stuff_en = 'eng' stuff_de = 'ger'
class Chapter(models.Model): number = models.PositiveIntegerField() name = TranslatedField( models.CharField(max_length=128) ) description = TranslatedField( models.CharField(max_length=256) ) def __str__(self): return f'{self.name}' class Meta: ordering = ['number']
class SpecificModel(models.Model): name = TranslatedField( models.CharField(_("name"), max_length=200, blank=True), {"en": { "blank": False }}, )
class Chunk(models.Model): """ A Chunk is a piece of content associated with a unique key that can be inserted into any template with the use of a special template tag. """ page = models.ForeignKey(Page, on_delete=models.CASCADE, related_name='chunks') key = models.CharField(_('str_Key'), help_text=_("str_Chunk_Key_help_text"), blank=False, max_length=255, unique=True) display = models.BooleanField(default=True, verbose_name=_('str_Display'), help_text=_('str_chunk_display_help_text')) content = TranslatedField( HTMLField(_('str_Content'), blank=True), {settings.LANGUAGES[0][0]: { "blank": True }}, attrgetter=fallback_to_default, ) description = TranslatedField( models.CharField(_('str_Description'), blank=True, max_length=64, help_text=_("str_Short_description")), {settings.LANGUAGES[0][0]: { "blank": True }}, attrgetter=fallback_to_default, ) objects = ChunkManager() class Meta: verbose_name = _('str_chunk') verbose_name_plural = _('str_chunks') def __unicode__(self): return u"%s" % (self.key, ) def __str__(self): return self.key
class Article(models.Model): number = models.PositiveIntegerField(unique=True) content = TranslatedField( models.CharField(max_length=1024) ) video_link = models.URLField(max_length=128) description = TranslatedField( models.CharField(max_length=256) ) date = models.DateField() section = models.ForeignKey('Section', related_name='articles', on_delete=models.CASCADE) def __str__(self): return f'{self.number}' class Meta: ordering = ['number']
class Section(models.Model): number = models.PositiveIntegerField() name = TranslatedField( models.CharField(max_length=128) ) chapter = models.ForeignKey('Chapter', related_name='sections', on_delete=models.CASCADE) def __str__(self): return f'{self.name}' class Meta: ordering = ['number']
class Article(models.Model): title = TranslatedField( models.CharField(_('title'), max_length=200) ) image = models.ImageField(_('image'), upload_to="images") publish = models.DateTimeField(_('publish'), default=timezone.now) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) status = models.BooleanField(_('status'), default=False) class Meta: ordering = ['-publish'] verbose_name = _("Article") verbose_name_plural = _("Articles") def __str__(self): return self.title
class Topic(models.Model): """FAQ Topics.""" topic_name = TranslatedField( models.CharField( max_length=200, verbose_name=_("str_Topic")), {settings.LANGUAGES[0][0]: {"blank": False}}, attrgetter=fallback_to_default,) number = models.PositiveIntegerField(unique=True) class Meta: verbose_name = _("str_Topic") verbose_name_plural = _("str_Topics") ordering = ['number'] def __unicode__(self): return u'(%s) %s' % (self.number, self.topic_name, ) def __str__(self): return self.topic_name
class MySlide(DirtyFieldsMixin, MultilingualMixin, RulesModel): REQUIRED_TRANSLATED_FIELDS = ('description',) location = models.CharField(verbose_name=_("Location"), max_length=12, choices=AVAILABLE_SLIDES, db_index=True) position = models.PositiveIntegerField(verbose_name=_("Position"), db_index=True, default=0) description = TranslatedField( models.TextField(verbose_name=_("Description"), null=True, blank=True), attrgetter=attrgetter) image = models.ForeignKey(MyFile, verbose_name=_("Image"), on_delete=models.CASCADE) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, db_index=True, related_name='created_slides') created_at = models.DateTimeField(auto_now_add=True, editable=False) modified_at = models.DateTimeField(auto_now=True, editable=False) class Meta: get_latest_by = 'created_at' ordering = ['-created_at'] rules_permissions = { 'add': rules.is_authenticated, 'read': rules.always_allow, 'change': rules.is_staff, 'delete': rules.is_staff, } def __str__(self): # pylint:disable=invalid-str-returned return self.location
class TranslatedFieldsMovie(models.Model): year = models.IntegerField() title = TranslatedField(models.CharField("Title", max_length=190))
class User(PermissionsMixin, Entity, AbstractBaseUser): LOCALIZABLE_FIELDS = ('first_name', 'last_name', 'city') NAME_LOCALIZABLE_FIELDS = LOCALIZABLE_FIELDS[:2] GENDER_UNKNOWN = 0 GENDER_FEMALE = 1 GENDER_MALE = 2 GENDER_OTHER = 3 GENDER_MAX_VALUE_PLUS_ONE = 4 GENDER_FEMALE_STRING = 'female' GENDER_MALE_STRING = 'male' GENDER_OTHER_STRING = 'other' GENDER_CHOICES = ( (GENDER_FEMALE, _("Female")), (GENDER_MALE, _("Male")), (GENDER_OTHER, _("Other")), ) GENDER_VALID_VALUES = [choice[0] for choice in GENDER_CHOICES] GENDERS_DICT = { GENDER_FEMALE: GENDER_FEMALE_STRING, GENDER_MALE: GENDER_MALE_STRING, GENDER_OTHER: GENDER_OTHER_STRING } DIET_UNKNOWN = 0 DIET_VEGAN = 1 DIET_VEGETARIAN = 2 DIET_CARNIST = 3 DIET_MAX_VALUE_PLUS_ONE = 4 DIET_CHOICES_WITH_DEFAULT = ( (DIET_UNKNOWN, _("Unknown")), (DIET_VEGAN, _("Vegan (eats only plants and fungi)")), (DIET_VEGETARIAN, _("Vegetarian (doesn't eat fish and meat)")), (DIET_CARNIST, _("Carnist (eats animals)")), ) DIET_VALID_CHOICES = DIET_CHOICES_WITH_DEFAULT[1:] DIET_VALID_VALUES = [choice[0] for choice in DIET_VALID_CHOICES] SMOKING_STATUS_UNKNOWN = 0 SMOKING_STATUS_NOT_SMOKING = 1 SMOKING_STATUS_SMOKING_OCCASIONALLY = 2 SMOKING_STATUS_SMOKING = 3 SMOKING_STATUS_MAX_VALUE_PLUS_ONE = 4 SMOKING_STATUS_CHOICES_WITH_DEFAULT = ( (SMOKING_STATUS_UNKNOWN, _("Unknown")), (SMOKING_STATUS_NOT_SMOKING, _("Not smoking")), (SMOKING_STATUS_SMOKING_OCCASIONALLY, _("Smoking occasionally")), (SMOKING_STATUS_SMOKING, _("Smoking")), ) SMOKING_STATUS_VALID_CHOICES = SMOKING_STATUS_CHOICES_WITH_DEFAULT[1:] SMOKING_STATUS_VALID_VALUES = [ choice[0] for choice in SMOKING_STATUS_VALID_CHOICES ] RELATIONSHIP_STATUS_UNKNOWN = 0 RELATIONSHIP_STATUS_SINGLE = 1 RELATIONSHIP_STATUS_DIVORCED = 2 RELATIONSHIP_STATUS_WIDOWED = 3 RELATIONSHIP_STATUS_IN_RELATIONSHIP = 4 RELATIONSHIP_STATUS_IN_OPEN_RELATIONSHIP = 5 RELATIONSHIP_STATUS_COMPLICATED = 6 RELATIONSHIP_STATUS_SEPARATED = 7 RELATIONSHIP_STATUS_ENGAGED = 8 RELATIONSHIP_STATUS_MARRIED = 9 RELATIONSHIP_STATUS_MAX_VALUE_PLUS_ONE = 10 RELATIONSHIP_STATUS_CHOICES_WITH_DEFAULT = ( (RELATIONSHIP_STATUS_UNKNOWN, _("Unknown")), (RELATIONSHIP_STATUS_SINGLE, _("Single")), (RELATIONSHIP_STATUS_DIVORCED, _("Divorced")), (RELATIONSHIP_STATUS_WIDOWED, _("Widowed")), (RELATIONSHIP_STATUS_IN_RELATIONSHIP, _("In a relationship")), (RELATIONSHIP_STATUS_IN_OPEN_RELATIONSHIP, _("In an open relationship")), (RELATIONSHIP_STATUS_COMPLICATED, _("It's complicated")), (RELATIONSHIP_STATUS_SEPARATED, _("Separated")), (RELATIONSHIP_STATUS_ENGAGED, _("Engaged")), (RELATIONSHIP_STATUS_MARRIED, _("Married")), ) RELATIONSHIP_STATUS_VALID_CHOICES = RELATIONSHIP_STATUS_CHOICES_WITH_DEFAULT[ 1:] RELATIONSHIP_STATUS_VALID_VALUES = [ choice[0] for choice in RELATIONSHIP_STATUS_VALID_CHOICES ] NOTIFICATIONS_OFF = 0 NOTIFICATIONS_ON = 1 NOTIFICATIONS_CHOICES = ( (NOTIFICATIONS_ON, _("Notify me")), (NOTIFICATIONS_OFF, _("Don't notify me")), ) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [ 'first_name', 'last_name', 'date_of_birth', 'gender', 'diet', 'slug' ] @staticmethod def diet_choices_with_description(gender): return ( (__class__.DIET_VEGAN, pgettext_lazy(context=gender, message="Vegan (eats only plants and fungi)")), (__class__.DIET_VEGETARIAN, pgettext_lazy(context=gender, message="Vegetarian (doesn't eat fish and meat)")), (__class__.DIET_CARNIST, pgettext_lazy(context=gender, message="Carnist (eats animals)")), ) @staticmethod def diet_choices(gender): return ( (__class__.DIET_VEGAN, pgettext_lazy(context=gender, message="Vegan")), (__class__.DIET_VEGETARIAN, pgettext_lazy(context=gender, message="Vegetarian")), (__class__.DIET_CARNIST, pgettext_lazy(context=gender, message="Carnist")), ) @staticmethod def smoking_status_choices(gender): return ( (__class__.SMOKING_STATUS_NOT_SMOKING, pgettext_lazy(context=gender, message="Not smoking")), (__class__.SMOKING_STATUS_SMOKING_OCCASIONALLY, pgettext_lazy(context=gender, message="Smoking occasionally")), (__class__.SMOKING_STATUS_SMOKING, pgettext_lazy(context=gender, message="Smoking")), ) @staticmethod def relationship_status_choices(gender): return ( (__class__.RELATIONSHIP_STATUS_SINGLE, pgettext_lazy(context=gender, message="Single")), (__class__.RELATIONSHIP_STATUS_DIVORCED, pgettext_lazy(context=gender, message="Divorced")), (__class__.RELATIONSHIP_STATUS_WIDOWED, pgettext_lazy(context=gender, message="Widowed")), (__class__.RELATIONSHIP_STATUS_IN_RELATIONSHIP, pgettext_lazy(context=gender, message="In a relationship")), (__class__.RELATIONSHIP_STATUS_IN_OPEN_RELATIONSHIP, pgettext_lazy(context=gender, message="In an open relationship")), (__class__.RELATIONSHIP_STATUS_COMPLICATED, pgettext_lazy(context=gender, message="It's complicated")), (__class__.RELATIONSHIP_STATUS_SEPARATED, pgettext_lazy(context=gender, message="Separated")), (__class__.RELATIONSHIP_STATUS_ENGAGED, pgettext_lazy(context=gender, message="Engaged")), (__class__.RELATIONSHIP_STATUS_MARRIED, pgettext_lazy(context=gender, message="Married")), ) first_name = TranslatedField(field=models.CharField( verbose_name=_('first name'), max_length=75), ) last_name = TranslatedField(field=models.CharField( verbose_name=_('last name'), max_length=150), ) gender = models.SmallIntegerField(verbose_name=_('I am'), choices=GENDER_CHOICES) date_of_birth = models.DateField(verbose_name=_('date of birth')) diet = models.SmallIntegerField(verbose_name=_('diet'), choices=DIET_CHOICES_WITH_DEFAULT, default=DIET_UNKNOWN) smoking_status = models.SmallIntegerField( verbose_name=_('smoking status'), choices=SMOKING_STATUS_CHOICES_WITH_DEFAULT, default=SMOKING_STATUS_UNKNOWN) relationship_status = models.SmallIntegerField( verbose_name=_('relationship status'), choices=RELATIONSHIP_STATUS_CHOICES_WITH_DEFAULT, default=RELATIONSHIP_STATUS_UNKNOWN) city = TranslatedField(field=models.CharField( verbose_name=_('city or locality'), max_length=120, blank=True, null=True), ) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) has_confirmed_email = models.BooleanField(default=False) access_dob_day_month = UserAccessField( verbose_name=_('Who can view my birth month and day'), default=UserAccessField.ACCESS_ME) access_dob_year = UserAccessField( verbose_name=_('Who can view my birth year'), default=UserAccessField.ACCESS_ME) notify_on_message = models.SmallIntegerField( verbose_name=_('On new messages'), choices=NOTIFICATIONS_CHOICES, default=NOTIFICATIONS_ON) objects = UserManager() @classproperty def settings(cls): return django_settings.USER_SETTINGS @classproperty def AGE_VALID_VALUES_IN_MODEL(cls): return range(cls.settings.MIN_AGE_ALLOWED_IN_MODEL, cls.settings.MAX_AGE_ALLOWED_IN_MODEL) @classproperty def AGE_VALID_VALUES_IN_FORMS(cls): return range(cls.settings.MIN_AGE_ALLOWED_IN_FORMS, cls.settings.MAX_AGE_ALLOWED_IN_FORMS) @classproperty def validators(cls): validators = { 'username': get_username_validators( min_username_length=cls.settings.MIN_USERNAME_LENGTH, max_username_length=cls.settings.MAX_USERNAME_LENGTH, allow_letters_after_digits=False), 'slug': get_slug_validators( min_username_length=cls.settings.MIN_USERNAME_LENGTH, max_username_length=cls.settings.MAX_USERNAME_LENGTH, min_slug_length=cls.settings.MIN_SLUG_LENGTH, max_slug_length=cls.settings.MAX_SLUG_LENGTH, allow_letters_after_digits=False) + ["validate_slug"], 'date_of_birth': [validate_date_of_birth_in_model], } return validators @property def name(self): return self.profile.get_name() @property def email(self): try: return self.email_addresses.get(is_primary=True).email except UserEmailAddress.DoesNotExist: return None @property def profile(self): if (not (hasattr(self, '_profile'))): self.refresh_all_profiles() return self._profile @property def speedy_net_profile(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_speedy_net_profile'))): self.refresh_all_profiles() return self._speedy_net_profile @property def speedy_match_profile(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_speedy_match_profile'))): self.refresh_all_profiles() return self._speedy_match_profile @property def received_friendship_requests(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_received_friendship_requests'))): self.refresh_all_friends_lists() return self._received_friendship_requests @property def received_friendship_requests_count(self): return len(self.received_friendship_requests) @property def sent_friendship_requests(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_sent_friendship_requests'))): self.refresh_all_friends_lists() return self._sent_friendship_requests @property def sent_friendship_requests_count(self): return len(self.sent_friendship_requests) @property def all_friends(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_friends'))): self.refresh_all_friends_lists() return self._friends @property def friends_count(self): return len(self.all_friends) @property def all_speedy_net_friends(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_speedy_net_friends'))): self.refresh_all_friends_lists() return self._speedy_net_friends @property def speedy_net_friends_count(self): return len(self.all_speedy_net_friends) @property def friends_trans(self): if (django_settings.SITE_ID == django_settings.SPEEDY_MATCH_SITE_ID): return pgettext_lazy( context=self.speedy_match_profile.get_match_gender(), message='Friends') else: return _('Friends') class Meta: verbose_name = _('user') verbose_name_plural = _('users') ordering = ('-last_login', 'id') swappable = 'AUTH_USER_MODEL' def __str__(self): # Depends on site: full name in Speedy Net, first name in Speedy Match. return '<User {} - {}/{}>'.format(self.id, self.name, self.slug) # return '<User {} - name={}, username={}, slug={}>'.format(self.id, self.name, self.username, self.slug) def _update_has_confirmed_email_field(self): speedy_net_site = Site.objects.get( pk=django_settings.SPEEDY_NET_SITE_ID) previous_has_confirmed_email = self.has_confirmed_email self.has_confirmed_email = ( self.email_addresses.filter(is_confirmed=True).count() > 0) self.save_user_and_profile() if (not (self.has_confirmed_email == previous_has_confirmed_email)): logger.info( 'User::_update_has_confirmed_email_field::User {user} has_confirmed_email is {has_confirmed_email} on {site_name}.' .format(site_name=_(speedy_net_site.name), user=self, has_confirmed_email=self.has_confirmed_email)) def save(self, *args, **kwargs): # Superuser must be equal to staff. if (not (self.is_superuser == self.is_staff)): raise ValidationError(_("Superuser must be equal to staff.")) return super().save(*args, **kwargs) def set_password(self, raw_password): password_validation.validate_password(password=raw_password) return super().set_password(raw_password=raw_password) def delete(self, *args, **kwargs): if ((self.is_staff) or (self.is_superuser)): warnings.warn('Can’t delete staff user.') return False else: self.email_addresses.all().delete() return super().delete(*args, **kwargs) def clean_fields(self, exclude=None): """ Allows to have different slug and username validators for Entity and User. """ if exclude is None: exclude = [] # If special username is true, don't validate username. if (self.special_username): # ~~~~ TODO: fix models! Exceptions should be 'slug' and not '__all__'. self.normalize_slug_and_username() self.validate_username_for_slug() self.validate_username_required() self.validate_username_unique() exclude += ['username', 'slug'] return super().clean_fields(exclude=exclude) def clean_all_fields(self, exclude=None): super().clean_all_fields(exclude=exclude) for base_field_name in __class__.NAME_LOCALIZABLE_FIELDS: self.clean_localizable_field(base_field_name=base_field_name) def clean_localizable_field(self, base_field_name): field_names = get_all_field_names(base_field_name=base_field_name) for field_name in field_names: if (not (string_is_not_empty(getattr(self, field_name)))): for _field_name in field_names: # Check again because maybe this field is already not empty. if (not (string_is_not_empty(getattr(self, field_name)))): if (string_is_not_empty(getattr(self, _field_name))): setattr(self, field_name, getattr(self, _field_name)) def get_absolute_url(self): return reverse('profiles:user', kwargs={'slug': self.slug}) def mail_user(self, template_name_prefix, context=None, send_to_unconfirmed=False): site = Site.objects.get_current() context = context or {} addresses = self.email_addresses.filter(is_primary=True) if (not (send_to_unconfirmed)): addresses = addresses.filter(is_confirmed=True) addresses = list(addresses) context.update({ 'site_name': _(site.name), 'user': self, }) if (addresses): return addresses[0].mail(template_name_prefix=template_name_prefix, context=context) return False def get_full_name(self): return '{} {}'.format(self.first_name, self.last_name).strip() or self.slug def get_first_name(self): return '{}'.format(self.first_name).strip() or self.slug def get_short_name(self): return self.get_first_name() @cached_property def has_confirmed_email_or_registered_now(self): return ((self.has_confirmed_email) or (self.date_created > now() - timedelta(hours=2))) def activate(self): self.is_active = True self.save_user_and_profile() def get_profile(self, model=None, profile_model=None) -> 'SiteProfileBase': if (model is None): model = get_site_profile_model(profile_model=profile_model) profile = getattr(self, model.RELATED_NAME, None) if (profile is None): profile = model.objects.get_or_create(user=self)[0] return profile def refresh_all_profiles(self): self._profile = self.get_profile() if (django_settings.LOGIN_ENABLED): from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile self._speedy_net_profile = self.get_profile( model=SpeedyNetSiteProfile) self._speedy_match_profile = self.get_profile( model=SpeedyMatchSiteProfile) def refresh_all_friends_lists(self): self._received_friendship_requests = self.get_received_friendship_requests( ) self._sent_friendship_requests = self.get_sent_friendship_requests() self._friends = self.get_friends() self._speedy_net_friends = self.get_speedy_net_friends() def get_received_friendship_requests(self): from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile SiteProfile = get_site_profile_model() qs = self.friendship_requests_received.all().prefetch_related( "from_user", "from_user__{}".format(SpeedyNetSiteProfile.RELATED_NAME), "from_user__{}".format( SpeedyMatchSiteProfile.RELATED_NAME)).distinct().order_by( '-from_user__{}__last_visit'.format( SiteProfile.RELATED_NAME)) received_friendship_requests = [ friendship_request for friendship_request in qs if (friendship_request.from_user.profile.is_active) ] if (django_settings.SITE_ID == django_settings.SPEEDY_NET_SITE_ID): return received_friendship_requests elif (django_settings.SITE_ID == django_settings.SPEEDY_MATCH_SITE_ID): from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile received_friendship_requests = [ friendship_request for friendship_request in received_friendship_requests if (self.speedy_match_profile.get_matching_rank( other_profile=friendship_request.from_user.profile) > SpeedyMatchSiteProfile.RANK_0) ] return received_friendship_requests else: raise NotImplementedError() def get_sent_friendship_requests(self): from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile SiteProfile = get_site_profile_model() qs = self.friendship_requests_sent.all().prefetch_related( "to_user", "to_user__{}".format(SpeedyNetSiteProfile.RELATED_NAME), "to_user__{}".format( SpeedyMatchSiteProfile.RELATED_NAME)).distinct().order_by( '-to_user__{}__last_visit'.format( SiteProfile.RELATED_NAME)) sent_friendship_requests = [ friendship_request for friendship_request in qs if (friendship_request.to_user.profile.is_active) ] if (django_settings.SITE_ID == django_settings.SPEEDY_NET_SITE_ID): return sent_friendship_requests elif (django_settings.SITE_ID == django_settings.SPEEDY_MATCH_SITE_ID): from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile sent_friendship_requests = [ friendship_request for friendship_request in sent_friendship_requests if (self.speedy_match_profile.get_matching_rank( other_profile=friendship_request.to_user.profile) > SpeedyMatchSiteProfile.RANK_0) ] return sent_friendship_requests else: raise NotImplementedError() def get_speedy_net_friends(self): from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile SiteProfile = get_site_profile_model() qs = self.friends.all().prefetch_related( "from_user", "from_user__{}".format(SpeedyNetSiteProfile.RELATED_NAME), "from_user__{}".format( SpeedyMatchSiteProfile.RELATED_NAME)).distinct().order_by( '-from_user__{}__last_visit'.format( SiteProfile.RELATED_NAME)) friends = [ friendship for friendship in qs if (friendship.from_user.speedy_net_profile.is_active) ] return friends def get_friends(self): from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile SiteProfile = get_site_profile_model() qs = self.friends.all().prefetch_related( "from_user", "from_user__{}".format(SpeedyNetSiteProfile.RELATED_NAME), "from_user__{}".format( SpeedyMatchSiteProfile.RELATED_NAME)).distinct().order_by( '-from_user__{}__last_visit'.format( SiteProfile.RELATED_NAME)) friends = [ friendship for friendship in qs if (friendship.from_user.profile.is_active) ] if (django_settings.SITE_ID == django_settings.SPEEDY_NET_SITE_ID): return friends elif (django_settings.SITE_ID == django_settings.SPEEDY_MATCH_SITE_ID): friends = [ friendship for friendship in friends if (self.speedy_match_profile.get_matching_rank( other_profile=friendship.from_user.profile) > SiteProfile.RANK_0) ] return friends else: raise NotImplementedError() def save_user_and_profile(self): with transaction.atomic(): self.save() self.profile.save() if (django_settings.LOGIN_ENABLED): self.speedy_net_profile.save() self.speedy_match_profile.save() def get_gender(self): return self.__class__.GENDERS_DICT.get(self.gender) def get_diet(self): diets = { self.__class__.DIET_VEGAN: pgettext_lazy(context=self.get_gender(), message="Vegan"), self.__class__.DIET_VEGETARIAN: pgettext_lazy(context=self.get_gender(), message="Vegetarian"), self.__class__.DIET_CARNIST: pgettext_lazy(context=self.get_gender(), message="Carnist"), } return diets.get(self.diet, "") def get_smoking_status(self): smoking_statuses = { self.__class__.SMOKING_STATUS_NOT_SMOKING: pgettext_lazy(context=self.get_gender(), message="Not smoking"), self.__class__.SMOKING_STATUS_SMOKING_OCCASIONALLY: pgettext_lazy(context=self.get_gender(), message="Smoking occasionally"), self.__class__.SMOKING_STATUS_SMOKING: pgettext_lazy(context=self.get_gender(), message="Smoking"), } return smoking_statuses.get(self.smoking_status, "") def get_relationship_status(self): relationship_statuses = { self.__class__.RELATIONSHIP_STATUS_SINGLE: pgettext_lazy(context=self.get_gender(), message="Single"), self.__class__.RELATIONSHIP_STATUS_DIVORCED: pgettext_lazy(context=self.get_gender(), message="Divorced"), self.__class__.RELATIONSHIP_STATUS_WIDOWED: pgettext_lazy(context=self.get_gender(), message="Widowed"), self.__class__.RELATIONSHIP_STATUS_IN_RELATIONSHIP: pgettext_lazy(context=self.get_gender(), message="In a relationship"), self.__class__.RELATIONSHIP_STATUS_IN_OPEN_RELATIONSHIP: pgettext_lazy(context=self.get_gender(), message="In an open relationship"), self.__class__.RELATIONSHIP_STATUS_COMPLICATED: pgettext_lazy(context=self.get_gender(), message="It's complicated"), self.__class__.RELATIONSHIP_STATUS_SEPARATED: pgettext_lazy(context=self.get_gender(), message="Separated"), self.__class__.RELATIONSHIP_STATUS_ENGAGED: pgettext_lazy(context=self.get_gender(), message="Engaged"), self.__class__.RELATIONSHIP_STATUS_MARRIED: pgettext_lazy(context=self.get_gender(), message="Married"), } return relationship_statuses.get(self.relationship_status, "") def get_age(self): return get_age(date_of_birth=self.date_of_birth) def get_diet_choices_with_description(self): return self.__class__.diet_choices_with_description( gender=self.get_gender()) def get_smoking_status_choices(self): return self.__class__.smoking_status_choices(gender=self.get_gender()) def get_relationship_status_choices(self): return self.__class__.relationship_status_choices( gender=self.get_gender())
class SiteProfile(SiteProfileBase): settings = django_settings.SPEEDY_MATCH_SITE_PROFILE_SETTINGS LOCALIZABLE_FIELDS = ('profile_description', 'city', 'children', 'more_children', 'match_description') RELATED_NAME = 'speedy_match_site_profile' HEIGHT_VALID_VALUES = range(settings.MIN_HEIGHT_ALLOWED, settings.MAX_HEIGHT_ALLOWED + 1) AGE_MATCH_VALID_VALUES = range(settings.MIN_AGE_MATCH_ALLOWED, settings.MAX_AGE_MATCH_ALLOWED + 1) SMOKING_STATUS_UNKNOWN = 0 SMOKING_STATUS_NO = 1 SMOKING_STATUS_SOMETIMES = 2 SMOKING_STATUS_YES = 3 SMOKING_STATUS_MAX_VALUE_PLUS_ONE = 4 SMOKING_STATUS_CHOICES_WITH_DEFAULT = ( (SMOKING_STATUS_UNKNOWN, _("Unknown")), (SMOKING_STATUS_NO, _("No")), (SMOKING_STATUS_SOMETIMES, _("Sometimes")), (SMOKING_STATUS_YES, _("Yes")), ) SMOKING_STATUS_VALID_CHOICES = SMOKING_STATUS_CHOICES_WITH_DEFAULT[1:] SMOKING_STATUS_VALID_VALUES = [ choice[0] for choice in SMOKING_STATUS_VALID_CHOICES ] MARITAL_STATUS_UNKNOWN = 0 MARITAL_STATUS_SINGLE = 1 MARITAL_STATUS_DIVORCED = 2 MARITAL_STATUS_WIDOWED = 3 MARITAL_STATUS_IN_RELATIONSHIP = 4 MARITAL_STATUS_IN_OPEN_RELATIONSHIP = 5 MARITAL_STATUS_COMPLICATED = 6 MARITAL_STATUS_SEPARATED = 7 MARITAL_STATUS_MARRIED = 8 MARITAL_STATUS_MAX_VALUE_PLUS_ONE = 9 MARITAL_STATUS_CHOICES_WITH_DEFAULT = ( (MARITAL_STATUS_UNKNOWN, _("Unknown")), (MARITAL_STATUS_SINGLE, _("Single")), (MARITAL_STATUS_DIVORCED, _("Divorced")), (MARITAL_STATUS_WIDOWED, _("Widowed")), (MARITAL_STATUS_IN_RELATIONSHIP, _("In a relatioship")), (MARITAL_STATUS_IN_OPEN_RELATIONSHIP, _("In an open relationship")), (MARITAL_STATUS_COMPLICATED, _("It's complicated")), (MARITAL_STATUS_SEPARATED, _("Separated")), (MARITAL_STATUS_MARRIED, _("Married")), ) MARITAL_STATUS_VALID_CHOICES = MARITAL_STATUS_CHOICES_WITH_DEFAULT[1:] MARITAL_STATUS_VALID_VALUES = [ choice[0] for choice in MARITAL_STATUS_VALID_CHOICES ] RANK_0 = 0 RANK_1 = 1 RANK_2 = 2 RANK_3 = 3 RANK_4 = 4 RANK_5 = 5 RANK_CHOICES = ( (RANK_0, _("0 hearts")), (RANK_1, _("1 hearts")), (RANK_2, _("2 hearts")), (RANK_3, _("3 hearts")), (RANK_4, _("4 hearts")), (RANK_5, _("5 hearts")), ) RANK_VALID_VALUES = [choice[0] for choice in RANK_CHOICES] @staticmethod def gender_to_match_default(): return list() @staticmethod def diet_match_default(): return dict( {str(diet): __class__.RANK_5 for diet in User.DIET_VALID_VALUES}) @staticmethod def smoking_status_match_default(): return dict({ str(smoking_status): __class__.RANK_5 for smoking_status in __class__.SMOKING_STATUS_VALID_VALUES }) @staticmethod def marital_status_match_default(): return dict({ str(marital_status): __class__.RANK_5 for marital_status in __class__.MARITAL_STATUS_VALID_VALUES }) @staticmethod def smoking_status_choices(gender): return ( # (__class__.SMOKING_STATUS_UNKNOWN, _("Unknown")), # ~~~~ TODO: remove this line! (__class__.SMOKING_STATUS_NO, pgettext_lazy(context=gender, message="No")), (__class__.SMOKING_STATUS_SOMETIMES, pgettext_lazy(context=gender, message="Sometimes")), (__class__.SMOKING_STATUS_YES, pgettext_lazy(context=gender, message="Yes")), ) @staticmethod def marital_status_choices(gender): return ( # (__class__.MARITAL_STATUS_UNKNOWN, _("Unknown")), # ~~~~ TODO: remove this line! (__class__.MARITAL_STATUS_SINGLE, pgettext_lazy(context=gender, message="Single")), (__class__.MARITAL_STATUS_DIVORCED, pgettext_lazy(context=gender, message="Divorced")), (__class__.MARITAL_STATUS_WIDOWED, pgettext_lazy(context=gender, message="Widowed")), (__class__.MARITAL_STATUS_IN_RELATIONSHIP, pgettext_lazy(context=gender, message="In a relatioship")), (__class__.MARITAL_STATUS_IN_OPEN_RELATIONSHIP, pgettext_lazy(context=gender, message="In an open relationship")), (__class__.MARITAL_STATUS_COMPLICATED, pgettext_lazy(context=gender, message="It's complicated")), (__class__.MARITAL_STATUS_SEPARATED, pgettext_lazy(context=gender, message="Separated")), (__class__.MARITAL_STATUS_MARRIED, pgettext_lazy(context=gender, message="Married")), ) user = models.OneToOneField(to=User, verbose_name=_('user'), primary_key=True, on_delete=models.CASCADE, related_name=RELATED_NAME) notify_on_like = models.PositiveIntegerField( verbose_name=_('on new likes'), choices=User.NOTIFICATIONS_CHOICES, default=User.NOTIFICATIONS_ON) active_languages = models.TextField(verbose_name=_('active languages'), blank=True) height = models.SmallIntegerField(verbose_name=_('height'), help_text=_('cm'), blank=True, null=True) # ~~~~ TODO: diet, smoking_status and marital_status - decide which model should contain them - are they relevant also to Speedy Net or only to Speedy Match? smoking_status = models.SmallIntegerField( verbose_name=_('smoking status'), choices=SMOKING_STATUS_CHOICES_WITH_DEFAULT, default=SMOKING_STATUS_UNKNOWN) marital_status = models.SmallIntegerField( verbose_name=_('marital status'), choices=MARITAL_STATUS_CHOICES_WITH_DEFAULT, default=MARITAL_STATUS_UNKNOWN) profile_description = TranslatedField( models.TextField(verbose_name=_('Few words about me'), blank=True, null=True)) city = TranslatedField( models.CharField(verbose_name=_('city or locality'), max_length=255, blank=True, null=True)) children = TranslatedField( models.TextField(verbose_name=_('Do you have children? How many?'), blank=True, null=True)) more_children = TranslatedField( models.TextField(verbose_name=_('Do you want (more) children?'), blank=True, null=True)) match_description = TranslatedField( models.TextField(verbose_name=_('My ideal match'), blank=True, null=True)) gender_to_match = ArrayField(models.SmallIntegerField(), verbose_name=_('Gender'), size=len(User.GENDER_VALID_VALUES), default=gender_to_match_default.__func__, blank=True, null=True) min_age_match = models.SmallIntegerField( verbose_name=_('minimal age to match'), default=settings.MIN_AGE_MATCH_ALLOWED) max_age_match = models.SmallIntegerField( verbose_name=_('maximal age to match'), default=settings.MAX_AGE_MATCH_ALLOWED) diet_match = JSONField(verbose_name=_('diet match'), default=diet_match_default.__func__) smoking_status_match = JSONField( verbose_name=_('smoking status match'), default=smoking_status_match_default.__func__) marital_status_match = JSONField( verbose_name=_('marital status match'), default=marital_status_match_default.__func__) activation_step = models.PositiveSmallIntegerField(default=2) objects = SiteProfileManager() @property def is_active(self): return ((self.user.is_active) and (get_language() in self.get_active_languages())) class Meta: verbose_name = _('Speedy Match Profile') verbose_name_plural = _('Speedy Match Profiles') def __str__(self): return '{} @ Speedy Match'.format(self.user) def _set_active_languages(self, languages): languages = sorted(list(set(languages))) self.active_languages = ','.join(set(languages)) def _deactivate_language(self, step): # Profile is invalid. Deactivate in this language. language_code = get_language() self._set_active_languages( set(self.get_active_languages()) - {language_code}) self.activation_step = step self.user.save_user_and_profile() def get_active_languages(self): return list( filter(None, (l.strip() for l in self.active_languages.split(',')))) def validate_profile_and_activate(self): # ~~~~ TODO: all the error messages in this function may depend on the current user's (or other user's) gender. from speedy.match.accounts import utils language_code = get_language() error_messages = [] for step in utils.get_steps_range(): fields = utils.get_step_fields_to_validate(step=step) for field_name in fields: try: utils.validate_field(field_name=field_name, user=self.user) except ValidationError as e: error_messages.append(str(e)) if (len(error_messages) > 0): self._deactivate_language(step=step) return step, error_messages # Registration form is complete. Check if the user has a confirmed email address. step = len(__class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS) if ((self.user.has_confirmed_email()) and (step >= self.activation_step)): # Profile is valid. Activate in this language. languages = self.get_active_languages() if (not (language_code in languages)): languages.append(language_code) self._set_active_languages(languages=languages) self.user.save_user_and_profile() else: self._deactivate_language(step=self.activation_step) error_messages.append(_("Please confirm your email address.")) return step, error_messages def call_after_verify_email_address(self): old_step = self.activation_step self.activation_step = len( __class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS) self.validate_profile_and_activate() self.activation_step = old_step self.user.save_user_and_profile() def get_matching_rank(self, other_profile, second_call=True) -> int: self.validate_profile_and_activate() try: other_profile.validate_profile_and_activate() except ValidationError as e: logger.error( "get_matching_rank::other_profile.validate_profile_and_activate() failed, other_profile.user.pk={other_user_pk}, other_profile.user.username={other_user_username}, other_profile.user.slug={other_user_slug}, e={e}" .format( other_user_pk=other_profile.user.pk, other_user_username=other_profile.user.username, other_user_slug=other_profile.user.slug, e=e, )) return self.__class__.RANK_0 if (self.user.pk == other_profile.user.pk): return self.__class__.RANK_0 if ((self.is_active) and (other_profile.is_active)): if (Block.objects.there_is_block(user_1=self.user, user_2=other_profile.user)): return self.__class__.RANK_0 if (other_profile.user.gender not in self.gender_to_match): return self.__class__.RANK_0 if (not (self.min_age_match <= other_profile.user.get_age() <= self.max_age_match)): return self.__class__.RANK_0 if (other_profile.user.diet == User.DIET_UNKNOWN): return self.__class__.RANK_0 if (other_profile.smoking_status == self.__class__.SMOKING_STATUS_UNKNOWN): return self.__class__.RANK_0 if (other_profile.marital_status == self.__class__.MARITAL_STATUS_UNKNOWN): return self.__class__.RANK_0 diet_rank = self.diet_match.get(str(other_profile.user.diet), self.__class__.RANK_5) smoking_status_rank = self.smoking_status_match.get( str(other_profile.smoking_status), self.__class__.RANK_5) marital_status_rank = self.marital_status_match.get( str(other_profile.marital_status), self.__class__.RANK_5) rank = min([diet_rank, smoking_status_rank, marital_status_rank]) if (rank > self.__class__.RANK_0) and (second_call): other_user_rank = other_profile.get_matching_rank( other_profile=self, second_call=False) if (other_user_rank == self.__class__.RANK_0): rank = self.__class__.RANK_0 other_profile.rank = rank return rank else: return self.__class__.RANK_0 def deactivate(self): self._set_active_languages([]) self.activation_step = 0 self.user.save_user_and_profile() def get_name(self): # Speedy Match name is user's first name. return self.user.get_first_name() def get_match_gender(self): if (len(self.gender_to_match) == 1): return User.GENDERS_DICT.get(self.gender_to_match[0]) else: return User.GENDERS_DICT.get(User.GENDER_OTHER) def get_smoking_status_choices(self): return self.__class__.smoking_status_choices( gender=self.user.get_gender()) def get_marital_status_choices(self): return self.__class__.marital_status_choices( gender=self.user.get_gender()) def get_diet_match_choices(self): return User.diet_choices(gender=self.get_match_gender()) def get_smoking_status_match_choices(self): return self.__class__.smoking_status_choices( gender=self.get_match_gender()) def get_marital_status_match_choices(self): return self.__class__.marital_status_choices( gender=self.get_match_gender())
class FAQ(models.Model): question = TranslatedField( models.CharField(_("question"), max_length=200, null=True), ) answer = TranslatedField( models.CharField(_("answer"), max_length=200, null=True), )
class BlogPosting(DirtyFieldsMixin, MultilingualMixin, RulesModel): REQUIRED_TRANSLATED_FIELDS = ('title', 'body', 'summary', 'slug') title = TranslatedField(models.CharField(verbose_name=_("Title"), max_length=65, blank=True), {settings.LANGUAGE_CODE: { 'blank': False }}, attrgetter=attrgetter) body = TranslatedField(models.TextField(verbose_name=_("Body"), blank=True), {settings.LANGUAGE_CODE: { 'blank': False }}, attrgetter=attrgetter) summary = TranslatedField(models.TextField(verbose_name=_("Summary"), blank=True), {settings.LANGUAGE_CODE: { 'blank': False }}, attrgetter=attrgetter) slug = TranslatedField(models.SlugField( verbose_name=_("URL Name"), max_length=64, unique=True, db_index=True, blank=True, null=True, help_text=_("Human friendly unique url to identify the " "content, will automatically be filled if left empty.")), {settings.LANGUAGE_CODE: { 'null': False }}, attrgetter=attrgetter) image = models.ImageField(verbose_name=_("Image"), upload_to='blog_posting/original/', width_field='image_width', height_field='image_height', null=True, blank=True) image_height = models.SmallIntegerField(null=True, editable=False) image_width = models.SmallIntegerField(null=True, editable=False) published_at = models.DateTimeField(verbose_name=_("Publish Date"), db_index=True, null=True, blank=True) published_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True, related_name='published_blogpostings') created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, db_index=True, related_name='created_blogpostings') created_at = models.DateTimeField(auto_now_add=True, editable=False) modified_at = models.DateTimeField(auto_now=True, editable=False) deleted_at = models.DateTimeField(db_index=True, null=True, blank=True) image_xs = models.ImageField(upload_to='blog_posting/image_xs/', null=True, editable=False) image_sm = models.ImageField(upload_to='blog_posting/image_sm/', null=True, editable=False) image_md = models.ImageField(upload_to='blog_posting/image_md/', null=True, editable=False) image_lg = models.ImageField(upload_to='blog_posting/image_lg/', null=True, editable=False) objects = BlogPostingManager() keywords = GenericRelation(ThingKeywordField) class Meta: get_latest_by = 'published_at' ordering = ['-modified_at'] rules_permissions = { 'add': rules.is_authenticated, 'read': rules.always_allow, 'change': rules.is_staff, 'delete': rules.is_staff, } def __str__(self): # pylint:disable=invalid-str-returned return self.title def get_absolute_url(self): with translation.override(self.valid_language()): return reverse('BlogPostingLang:Display', args=(self.slug, 'html')) def get_natural_key(self): return (self.slug, ) def get_image_url(self): return reverse('BlogPosting:Image', args=(self.pk, 'lg')) def is_published(self): return self.published_at and not self.deleted_at def save(self, update_fields=None, **kwargs): dirty = self.get_dirty_fields() if not update_fields is None: dirty = {key: val for (key, val) in dirty.items()\ if key in update_fields} for langcode, _ in settings.LANGUAGES: title_field = to_attribute('title', langcode) slug_field = to_attribute('slug', langcode) if title_field in dirty and getattr(self, title_field) and\ (slug_field not in dirty or not getattr(self, slug_field)): setattr(self, slug_field, slugify(getattr(self, title_field))) if update_fields and slug_field not in update_fields: update_fields.append(slug_field) return super().save(update_fields=update_fields, **kwargs) def get_html_attr_srcset(self): attribute_value = [] for name, size, _ in settings.IMAGE_SIZES: imgfield = getattr(self, f'image_{name}') if not imgfield: continue attribute_value.append( '%s %sw' % (reverse('BlogPosting:Image', args=(self.pk, name)), size[0])) return ', '.join(attribute_value) def get_html_attr_sizes(self): attribute_value = [] for name, size, viewport_width in settings.IMAGE_SIZES: imgfield = getattr(self, f'image_{name}') if not imgfield: continue if viewport_width: attribute_value.append('(max-width: %spx) %spx' % (viewport_width, size[0])) else: attribute_value.append('%spx' % size[0]) return ', '.join(attribute_value) @staticmethod def get_microdata_type(): return 'http://schema.org/BlogPosting'
class Event(DirtyFieldsMixin, MultilingualMixin, RulesModel): REQUIRED_TRANSLATED_FIELDS = ('title', 'body', 'summary', 'slug') started_at = models.DateTimeField(verbose_name=_("Started At"), db_index=True) stopped_at = models.DateTimeField(verbose_name=_("Stopped At"), db_index=True, null=True, blank=True) title = TranslatedField(models.CharField(verbose_name=_("Title"), max_length=65, blank=True), {settings.LANGUAGE_CODE: { 'blank': False }}, attrgetter=attrgetter) body = TranslatedField(models.TextField(verbose_name=_("Body"), blank=True), {settings.LANGUAGE_CODE: { 'blank': False }}, attrgetter=attrgetter) summary = TranslatedField(models.TextField(verbose_name=_("Summary"), blank=True), {settings.LANGUAGE_CODE: { 'blank': False }}, attrgetter=attrgetter) slug = TranslatedField(models.SlugField( verbose_name=_("URL Name"), max_length=64, unique=True, db_index=True, blank=True, help_text=_("Human friendly unique url to identify the " "content, will automatically be filled if left empty.")), attrgetter=attrgetter) published_at = models.DateTimeField(verbose_name=_("Publish Date"), db_index=True, null=True, blank=True) published_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True, related_name='published_events') created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, db_index=True, related_name='created_events') created_at = models.DateTimeField(auto_now_add=True, editable=False) modified_at = models.DateTimeField(auto_now=True, editable=False) deleted_at = models.DateTimeField(db_index=True, null=True, blank=True) objects = EventManager() class Meta: get_latest_by = 'created_at' ordering = ['-created_at'] rules_permissions = { 'add': rules.is_staff, 'read': rules.always_allow, 'change': rules.is_staff, 'delete': rules.is_staff, } def __str__(self): # pylint:disable=invalid-str-returned return self.title def get_absolute_url(self): with translation.override(self.valid_language()): return reverse('EventLang:Display', args=(self.slug, 'html')) def get_natural_key(self): return (self.slug, ) def is_published(self): return self.published_at and not self.deleted_at def save(self, update_fields=None, **kwargs): dirty = self.get_dirty_fields() if not update_fields is None: dirty = {key: val for (key, val) in dirty.items()\ if key in update_fields} for langcode, _ in settings.LANGUAGES: title_field = to_attribute('title', langcode) slug_field = to_attribute('slug', langcode) if title_field in dirty and getattr(self, title_field) and\ (slug_field not in dirty or not getattr(self, slug_field)): setattr(self, slug_field, slugify(getattr(self, title_field))) if update_fields and slug_field not in update_fields: update_fields.append(slug_field) return super().save(update_fields=update_fields, **kwargs)
class ModelWithAnyFallback(models.Model): optional = TranslatedField( models.CharField(_("optional"), max_length=20, blank=True), attrgetter=fallback_to_any, )
class SiteProfile(SiteProfileBase): RELATED_NAME = 'speedy_net_site_profile' user = models.OneToOneField(to=User, verbose_name=_('User'), primary_key=True, on_delete=models.CASCADE, related_name=RELATED_NAME) is_active = models.BooleanField(default=True) number_of_friends = TranslatedField(field=models.PositiveSmallIntegerField( verbose_name=_("Number of friends on last user's visit"), default=None, blank=True, null=True), ) @property def is_active_and_valid(self): return (self.is_active) class Meta: verbose_name = _('Speedy Net Profile') verbose_name_plural = _('Speedy Net Profiles') def __str__(self): return '{} @ Speedy Net'.format(super().__str__()) def save(self, *args, **kwargs): self._update_number_of_friends() return super().save(*args, **kwargs) def _update_number_of_friends(self): speedy_net_site = Site.objects.get( pk=django_settings.SPEEDY_NET_SITE_ID) previous_number_of_friends = self.number_of_friends self.number_of_friends = self.user.speedy_net_friends_count if (not (self.number_of_friends == previous_number_of_friends)): logger.info( 'SpeedyNetSiteProfile::_update_number_of_friends::User {user} has {number_of_friends} friends on {site_name}.' .format(site_name=_(speedy_net_site.name), user=self.user, number_of_friends=self.number_of_friends)) if (self.number_of_friends > User.settings.MAX_NUMBER_OF_FRIENDS_ALLOWED - 20): logger.warning( 'SpeedyNetSiteProfile::_update_number_of_friends::User {user} has more than {number_of_friends} friends on {site_name}.' .format(site_name=_(speedy_net_site.name), user=self.user, number_of_friends=User.settings. MAX_NUMBER_OF_FRIENDS_ALLOWED - 20)) if (self.number_of_friends > User.settings.MAX_NUMBER_OF_FRIENDS_ALLOWED): logger.error( 'SpeedyNetSiteProfile::_update_number_of_friends::User {user} has more than {MAX_NUMBER_OF_FRIENDS_ALLOWED} friends on {site_name}.' .format(site_name=_(speedy_net_site.name), user=self.user, MAX_NUMBER_OF_FRIENDS_ALLOWED=User.settings. MAX_NUMBER_OF_FRIENDS_ALLOWED)) def activate(self): self.is_active = True self.user.is_active = True self.user.save_user_and_profile() def deactivate(self): self.is_active = False if (not (self.user.is_superuser)): self.user.is_active = False self.user.save_user_and_profile() def get_name(self): return self.user.get_full_name() def call_after_verify_email_address(self): pass
class MyFile(DirtyFieldsMixin, MultilingualMixin, RulesModel): REQUIRED_TRANSLATED_FIELDS = ('description',) filehash = models.CharField(verbose_name=_("Title"), max_length=65, editable=False) mimetype = models.CharField(verbose_name=_("Mime Type"), max_length=50, editable=False) description = TranslatedField( models.TextField(verbose_name=_("Description"), blank=True), {settings.LANGUAGE_CODE: {'blank': False}}, attrgetter=attrgetter) alt_text = TranslatedField( models.TextField(verbose_name=_("Alternate Text (alt text)"), null=True, blank=True, help_text=_("Only need to fill this if you were uploading " "an image.")), attrgetter=attrgetter) databits = models.FileField(verbose_name=_("Content"), upload_to='files/original/') created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, db_index=True, related_name='created_files') created_at = models.DateTimeField(auto_now_add=True, editable=False) modified_at = models.DateTimeField(auto_now=True, editable=False) deleted_at = models.DateTimeField(db_index=True, null=True, blank=True) image_xs = models.ImageField(upload_to='files/image_xs/', null=True, editable=False) image_sm = models.ImageField(upload_to='files/image_sm/', null=True, editable=False) image_md = models.ImageField(upload_to='files/image_md/', null=True, editable=False) image_lg = models.ImageField(upload_to='files/image_lg/', null=True, editable=False) objects = MyFileManager() class Meta: get_latest_by = 'created_at' ordering = ['-created_at'] rules_permissions = { 'add': rules.is_authenticated, 'read': rules.always_allow, 'change': rules.is_staff, 'delete': rules.is_staff, } def __str__(self): # pylint:disable=invalid-str-returned return self.filehash def get_absolute_url(self): if self.is_image(): return reverse('MyFile:Display', args=(self.pk, 'xs')) return reverse('MyFile:Display', args=(self.pk,)) def get_natural_key(self): return (self.filehash,) def is_image(self): return self.mimetype.startswith('image/') def get_filename(self): return os.path.basename(self.databits.name) def get_html_attr_srcset(self): attribute_value = [] for name, size, _ in settings.IMAGE_SIZES: imgfield = getattr(self, f'image_{name}') if not imgfield: continue attribute_value.append('%s %sw' % ( reverse('MyFile:Display', args=(self.pk, name)), size[0])) return ', '.join(attribute_value) def get_html_attr_sizes(self): attribute_value = [] for name, size, viewport_width in settings.IMAGE_SIZES: imgfield = getattr(self, f'image_{name}') if not imgfield: continue if viewport_width: attribute_value.append('(max-width: %spx) %spx' % ( viewport_width, size[0])) else: attribute_value.append('%spx' % size[0]) return ', '.join(attribute_value)
class CustomLanguagesModel(models.Model): name = TranslatedField( models.CharField(_("name"), max_length=200), languages=("fr", "it"), attrgetter=custom_attrgetter, )
class CustomLanguagesModel(models.Model): name = TranslatedField( models.CharField(_('name'), max_length=200), languages=('fr', 'it'), attrgetter=custom_attrgetter, )
class SpecificModel(models.Model): name = TranslatedField( models.CharField(_('name'), max_length=200, blank=True), {'en': {'blank': False}}, )
class SiteProfile(SiteProfileBase): LOCALIZABLE_FIELDS = ('profile_description', 'children', 'more_children', 'match_description') RELATED_NAME = 'speedy_match_site_profile' RANK_0 = 0 RANK_1 = 1 RANK_2 = 2 RANK_3 = 3 RANK_4 = 4 RANK_5 = 5 RANK_CHOICES = ( (RANK_0, _("No match")), (RANK_1, _("One heart")), (RANK_2, _("Two hearts")), (RANK_3, _("Three hearts")), (RANK_4, _("Four hearts")), (RANK_5, _("Five hearts")), ) RANK_VALID_VALUES = [choice[0] for choice in RANK_CHOICES] @staticmethod def active_languages_default(): return list() @staticmethod def gender_to_match_default(): return list() @staticmethod def min_age_to_match_default(): return __class__.settings.MIN_AGE_TO_MATCH_ALLOWED @staticmethod def max_age_to_match_default(): return __class__.settings.MAX_AGE_TO_MATCH_ALLOWED @staticmethod def diet_match_default(): return dict( {str(diet): __class__.RANK_5 for diet in User.DIET_VALID_VALUES}) @staticmethod def smoking_status_match_default(): return dict({ str(smoking_status): __class__.RANK_5 for smoking_status in User.SMOKING_STATUS_VALID_VALUES }) @staticmethod def relationship_status_match_default(): return dict({ str(relationship_status): __class__.RANK_5 for relationship_status in User.RELATIONSHIP_STATUS_VALID_VALUES }) @staticmethod def diet_to_match_default(): return list() @staticmethod def smoking_status_to_match_default(): return list() @staticmethod def relationship_status_to_match_default(): return list() @staticmethod def get_rank_description(rank): rank_descriptions = { __class__.RANK_0: _("No match"), __class__.RANK_1: _("One heart"), __class__.RANK_2: _("Two hearts"), __class__.RANK_3: _("Three hearts"), __class__.RANK_4: _("Four hearts"), __class__.RANK_5: _("Five hearts"), } return rank_descriptions.get(rank, "") user = models.OneToOneField(to=User, verbose_name=_('User'), primary_key=True, on_delete=models.CASCADE, related_name=RELATED_NAME) notify_on_like = models.SmallIntegerField( verbose_name=_('On new likes'), choices=User.NOTIFICATIONS_CHOICES, default=User.NOTIFICATIONS_ON) active_languages = ArrayField( base_field=models.CharField(max_length=2, choices=django_settings.LANGUAGES), verbose_name=_('Active languages'), size=len(django_settings.LANGUAGES), default=active_languages_default.__func__, blank=True, null=True, ) height = models.SmallIntegerField(verbose_name=_('Height'), help_text=_('cm'), blank=True, null=True) profile_description = TranslatedField(field=models.TextField( verbose_name=_('Few words about me'), max_length=50000, validators=[MaxLengthValidator(limit_value=50000)], blank=True, null=True), ) children = TranslatedField(field=models.TextField( verbose_name=_('Do you have children? How many?'), max_length=50000, validators=[MaxLengthValidator(limit_value=50000)], blank=True, null=True), ) more_children = TranslatedField(field=models.TextField( verbose_name=_('Do you want (more) children?'), max_length=50000, validators=[MaxLengthValidator(limit_value=50000)], blank=True, null=True), ) match_description = TranslatedField(field=models.TextField( verbose_name=_('My ideal match'), max_length=50000, validators=[MaxLengthValidator(limit_value=50000)], blank=True, null=True), ) gender_to_match = ArrayField( base_field=models.SmallIntegerField(choices=User.GENDER_CHOICES), verbose_name=_('Gender to match'), size=len(User.GENDER_VALID_VALUES), default=gender_to_match_default.__func__, blank=True, null=True, ) min_age_to_match = models.SmallIntegerField( verbose_name=_('Minimal age to match'), default=min_age_to_match_default.__func__) max_age_to_match = models.SmallIntegerField( verbose_name=_('Maximal age to match'), default=max_age_to_match_default.__func__) diet_match = JSONField(verbose_name=_('Diet match'), default=diet_match_default.__func__) smoking_status_match = JSONField( verbose_name=_('Smoking status match'), default=smoking_status_match_default.__func__) relationship_status_match = JSONField( verbose_name=_('Relationship status match'), default=relationship_status_match_default.__func__) diet_to_match = ArrayField( base_field=models.SmallIntegerField(choices=User.DIET_VALID_CHOICES), verbose_name=_('Diet to match'), size=len(User.DIET_VALID_VALUES), default=diet_to_match_default.__func__, blank=True, null=True, ) smoking_status_to_match = ArrayField( base_field=models.SmallIntegerField( choices=User.SMOKING_STATUS_VALID_CHOICES), verbose_name=_('Smoking status to match'), size=len(User.SMOKING_STATUS_VALID_VALUES), default=smoking_status_to_match_default.__func__, blank=True, null=True, ) relationship_status_to_match = ArrayField( base_field=models.SmallIntegerField( choices=User.RELATIONSHIP_STATUS_VALID_CHOICES), verbose_name=_('Relationship status to match'), size=len(User.RELATIONSHIP_STATUS_VALID_VALUES), default=relationship_status_to_match_default.__func__, blank=True, null=True, ) activation_step = TranslatedField(field=models.PositiveSmallIntegerField( verbose_name=_('Activation step'), default=2), ) number_of_matches = TranslatedField(field=models.PositiveSmallIntegerField( verbose_name=_("Number of matches on last user's search"), default=None, blank=True, null=True), ) profile_picture_months_offset = models.PositiveSmallIntegerField( default=5 ) # If a face is detected, will be 0. Otherwise, will be 5 months. not_allowed_to_use_speedy_match = models.BooleanField( default=False) # If set to True, user will have no matches. likes_to_user_count = models.PositiveSmallIntegerField(default=0, blank=True, null=True) objects = SiteProfileManager() @classproperty def settings(cls): return django_settings.SPEEDY_MATCH_SITE_PROFILE_SETTINGS @classproperty def HEIGHT_VALID_VALUES(cls): return range(cls.settings.MIN_HEIGHT_ALLOWED, cls.settings.MAX_HEIGHT_ALLOWED + 1) @classproperty def AGE_TO_MATCH_VALID_VALUES(cls): return range(cls.settings.MIN_AGE_TO_MATCH_ALLOWED, cls.settings.MAX_AGE_TO_MATCH_ALLOWED + 1) @cached_property def is_active(self): return ((self.user.is_active) and (get_language() in self.active_languages)) @cached_property def is_active_and_valid(self): if (self.is_active): step, error_messages = self.validate_profile_and_activate( commit=False) error = False if (len(error_messages) > 0): error = True if (not (step == len( __class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS)) ): error = True if (error): logger.error( "is_active_and_valid::user is active but not valid, step={step}, error_messages={error_messages}, self.user.pk={self_user_pk}, self.user.username={self_user_username}, self.user.slug={self_user_slug} (registered {registered_days_ago} days ago)" .format( step=step, error_messages=error_messages, self_user_pk=self.user.pk, self_user_username=self.user.username, self_user_slug=self.user.slug, registered_days_ago=(now() - self.user.date_created).days, )) return False return (self.is_active) class Meta: verbose_name = _('Speedy Match Profile') verbose_name_plural = _('Speedy Match Profiles') ordering = ('-last_visit', 'user_id') def __str__(self): return '{} @ Speedy Match'.format(super().__str__()) def save(self, *args, **kwargs): if (hasattr(self, "_rank_dict")): delattr(self, "_rank_dict") self._set_values_to_match() if (self.activation_step < 2): self.activation_step = 2 if (self.activation_step > len( __class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS)): self.activation_step = len( __class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS) if ((self.is_active) and (self.activation_step < len( __class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS))): self._deactivate_language(step=self.activation_step, commit=False) if ((len(self.active_languages) > 0) and (not (self.user.has_confirmed_email))): self._set_active_languages(languages=[]) return super().save(*args, **kwargs) def _set_values_to_match(self): from speedy.match.accounts import utils self._set_active_languages(languages=self.active_languages) self.gender_to_match = sorted(list(set(self.gender_to_match))) errors = 0 for field_name in [ 'diet_match', 'smoking_status_match', 'relationship_status_match' ]: try: utils.validate_field(field_name=field_name, user=self.user) except (ValidationError, AttributeError): errors += 1 if (errors == 0): self.diet_to_match = [ diet for diet in User.DIET_VALID_VALUES if (self.diet_match[str(diet)] > self.__class__.RANK_0) ] self.smoking_status_to_match = [ smoking_status for smoking_status in User.SMOKING_STATUS_VALID_VALUES if (self.smoking_status_match[str(smoking_status)] > self.__class__.RANK_0) ] self.relationship_status_to_match = [ relationship_status for relationship_status in User.RELATIONSHIP_STATUS_VALID_VALUES if (self.relationship_status_match[str(relationship_status)] > self.__class__.RANK_0) ] else: self.diet_to_match = list() self.smoking_status_to_match = list() self.relationship_status_to_match = list() def _set_active_languages(self, languages): self.active_languages = sorted(list(set(languages))) if (hasattr(self, "is_active")): delattr(self, "is_active") if (hasattr(self, "is_active_and_valid")): delattr(self, "is_active_and_valid") def _deactivate_language(self, step, commit=True): # Profile is invalid. Deactivate in this language. language_code = get_language() self._set_active_languages(languages=set(self.active_languages) - {language_code}) self.activation_step = step if (commit): self.user.save_user_and_profile() def validate_profile_and_activate(self, commit=True): from speedy.match.accounts import utils language_code = get_language() error_messages = [] for step in utils.get_steps_range(): fields = utils.get_step_fields_to_validate(step=step) for field_name in fields: try: utils.validate_field(field_name=field_name, user=self.user) except ValidationError as e: error_messages.append(str(e)) if (len(error_messages) > 0): if (commit): self._deactivate_language(step=step) return step, error_messages # Check if the user is not allowed to use Speedy Match. if (self.not_allowed_to_use_speedy_match): step = len( __class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS) - 1 return step, error_messages # Registration form is complete. Check if the user has a confirmed email address. step = len(__class__.settings.SPEEDY_MATCH_SITE_PROFILE_FORM_FIELDS) if ((self.user.has_confirmed_email) and (step >= self.activation_step)): if (commit): # Profile is valid. Activate in this language. self.activation_step = step languages = self.active_languages if (not (language_code in languages)): languages.append(language_code) self._set_active_languages(languages=languages) self.user.save_user_and_profile() else: if (commit): self._deactivate_language(step=self.activation_step) error_messages.append(_("Please confirm your email address.")) return step, error_messages def call_after_verify_email_address(self): pass def _get_matching_rank(self, other_profile, second_call=True) -> int: self._get_matching_rank_calls = getattr( self, "_get_matching_rank_calls", 0) + 1 if (self._get_matching_rank_calls >= 5): logger.debug( '_get_matching_rank::_get_matching_rank_calls={_get_matching_rank_calls}, self={self}, other_profile={other_profile}' .format(_get_matching_rank_calls=self._get_matching_rank_calls, self=self, other_profile=other_profile)) if (self.user.pk == other_profile.user.pk): return self.__class__.RANK_0 if ((self.is_active_and_valid) and (other_profile.is_active_and_valid)): if (Block.objects.there_is_block(user_1=self.user, user_2=other_profile.user)): return self.__class__.RANK_0 if (not ( (__class__.settings.MIN_HEIGHT_TO_MATCH <= self.height <= __class__.settings.MAX_HEIGHT_TO_MATCH) and (__class__.settings.MIN_HEIGHT_TO_MATCH <= other_profile.height <= __class__.settings.MAX_HEIGHT_TO_MATCH))): return self.__class__.RANK_0 if (self.not_allowed_to_use_speedy_match or other_profile.not_allowed_to_use_speedy_match): return self.__class__.RANK_0 if (other_profile.user.gender not in self.gender_to_match): return self.__class__.RANK_0 if (not (self.min_age_to_match <= other_profile.user.get_age() <= self.max_age_to_match)): return self.__class__.RANK_0 if (other_profile.user.diet == User.DIET_UNKNOWN): return self.__class__.RANK_0 if (other_profile.user.smoking_status == User.SMOKING_STATUS_UNKNOWN): return self.__class__.RANK_0 if (other_profile.user.relationship_status == User.RELATIONSHIP_STATUS_UNKNOWN): return self.__class__.RANK_0 diet_rank = self.diet_match.get(str(other_profile.user.diet), self.__class__.RANK_0) smoking_status_rank = self.smoking_status_match.get( str(other_profile.user.smoking_status), self.__class__.RANK_0) relationship_status_rank = self.relationship_status_match.get( str(other_profile.user.relationship_status), self.__class__.RANK_0) rank = min( [diet_rank, smoking_status_rank, relationship_status_rank]) if (rank > self.__class__.RANK_0) and (second_call): other_user_rank = other_profile._get_matching_rank( other_profile=self, second_call=False) if (other_user_rank == self.__class__.RANK_0): rank = self.__class__.RANK_0 return rank else: if (self.is_active): if (not (self.is_active_and_valid)): logger.error( '_get_matching_rank::get inside "if (not (self.is_active_and_valid)):", self={self}, other_profile={other_profile}' .format(self=self, other_profile=other_profile)) if (other_profile.is_active): if (not (other_profile.is_active_and_valid)): logger.error( '_get_matching_rank::get inside "if (not (other_profile.is_active_and_valid)):", self={self}, other_profile={other_profile}' .format(self=self, other_profile=other_profile)) return self.__class__.RANK_0 def get_matching_rank(self, other_profile) -> int: if (self.user.pk == other_profile.user.pk): return self.__class__.RANK_0 rank_dict = getattr(self, "_rank_dict", {}) if (other_profile.user.pk in rank_dict): rank = rank_dict[other_profile.user.pk] else: rank = self._get_matching_rank(other_profile=other_profile) rank_dict[other_profile.user.pk] = rank self._rank_dict = rank_dict other_profile.rank = rank self.rank = self.__class__.RANK_0 return rank def deactivate(self): self._set_active_languages(languages=[]) self.activation_step = 2 for language_code, language_name in django_settings.LANGUAGES: setattr( self, to_attribute(name='activation_step', language_code=language_code), 2) self.user.save_user_and_profile() def get_name(self): # Speedy Match name is the user's first name. return self.user.get_first_name() def get_match_gender(self): if (len(self.gender_to_match) == 1): match_gender = User.GENDERS_DICT.get(self.gender_to_match[0]) else: match_gender = User.GENDERS_DICT.get(User.GENDER_OTHER) return match_gender def get_like_gender(self): # No need to query the database if len(self.gender_to_match) is not 1. if ((len(self.gender_to_match) == 1) and ({self.get_match_gender()} == { like.to_user.get_gender() for like in UserLike.objects.filter(from_user=self.user). prefetch_related("to_user").distinct() } | { like.from_user.get_gender() for like in UserLike.objects.filter(to_user=self.user). prefetch_related("from_user").distinct() } | {self.get_match_gender()})): like_gender = self.get_match_gender() else: like_gender = User.GENDERS_DICT.get(User.GENDER_OTHER) return like_gender def get_diet_match_choices(self): return User.diet_choices(gender=self.get_match_gender()) def get_smoking_status_match_choices(self): return User.smoking_status_choices(gender=self.get_match_gender()) def get_relationship_status_match_choices(self): return User.relationship_status_choices(gender=self.get_match_gender())
class Country(models.Model): name = TranslatedField( models.CharField(max_length=255, default=None, blank=True)) def __str__(self): return self.name
class User(PermissionsMixin, Entity, AbstractBaseUser): settings = django_settings.USER_SETTINGS # settings = speedy_net_global_settings.UserSettings # ~~~~ TODO: remove this line! LOCALIZABLE_FIELDS = ('first_name', 'last_name') AGE_VALID_VALUES_IN_MODEL = range(settings.MIN_AGE_ALLOWED_IN_MODEL, settings.MAX_AGE_ALLOWED_IN_MODEL) AGE_VALID_VALUES_IN_FORMS = range(settings.MIN_AGE_ALLOWED_IN_FORMS, settings.MAX_AGE_ALLOWED_IN_FORMS) GENDER_UNKNOWN = 0 GENDER_FEMALE = 1 GENDER_MALE = 2 GENDER_OTHER = 3 GENDER_MAX_VALUE_PLUS_ONE = 4 GENDER_FEMALE_STRING = 'female' GENDER_MALE_STRING = 'male' GENDER_OTHER_STRING = 'other' GENDER_CHOICES = ( (GENDER_FEMALE, _("Female")), (GENDER_MALE, _("Male")), (GENDER_OTHER, _("Other")), ) GENDER_VALID_VALUES = [choice[0] for choice in GENDER_CHOICES] GENDERS_DICT = { GENDER_FEMALE: GENDER_FEMALE_STRING, GENDER_MALE: GENDER_MALE_STRING, GENDER_OTHER: GENDER_OTHER_STRING } # print(GENDERS_DICT) # for debugging. # ~~~~ TODO: remove this line! # ALL_GENDERS = [GENDERS_DICT[gender] for gender in GENDER_VALID_VALUES] # ~~~~ TODO: remove this line! # ALL_GENDERS = GENDERS_DICT # ~~~~ TODO: remove this line! # ALL_GENDERS = [__class__.GENDERS_DICT[gender] for gender in __class__.GENDER_VALID_VALUES] # ~~~~ TODO: maybe rename to ALL_GENDERS_STRINGS? # ~~~~ TODO: remove this line! # ALL_GENDERS = [__class__.GENDERS_DICT[gender] for gender in __class__.GENDER_VALID_VALUES] # ~~~~ TODO: maybe rename to ALL_GENDERS_STRINGS? # ~~~~ TODO: remove this line! DIET_UNKNOWN = 0 DIET_VEGAN = 1 DIET_VEGETARIAN = 2 DIET_CARNIST = 3 DIET_MAX_VALUE_PLUS_ONE = 4 DIET_CHOICES_WITH_DEFAULT = ( (DIET_UNKNOWN, _("Unknown")), (DIET_VEGAN, _("Vegan (eats only plants and fungi)")), (DIET_VEGETARIAN, _("Vegetarian (doesn't eat fish and meat)")), (DIET_CARNIST, _("Carnist (eats animals)")), ) DIET_VALID_CHOICES = DIET_CHOICES_WITH_DEFAULT[1:] DIET_VALID_VALUES = [choice[0] for choice in DIET_VALID_CHOICES] NOTIFICATIONS_OFF = 0 NOTIFICATIONS_ON = 1 NOTIFICATIONS_CHOICES = ( (NOTIFICATIONS_ON, _("Notify me")), (NOTIFICATIONS_OFF, _("Don't notify me")), ) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [ 'first_name', 'last_name', 'date_of_birth', 'gender', 'diet', 'slug' ] @staticmethod def diet_choices(gender): return ( # (__class__.DIET_UNKNOWN, _("Unknown")), # ~~~~ TODO: remove this line! (__class__.DIET_VEGAN, pgettext_lazy(context=gender, message="Vegan (eats only plants and fungi)")), (__class__.DIET_VEGETARIAN, pgettext_lazy(context=gender, message="Vegetarian (doesn't eat fish and meat)")), (__class__.DIET_CARNIST, pgettext_lazy(context=gender, message="Carnist (eats animals)")), ) first_name = TranslatedField( models.CharField(verbose_name=_('first name'), max_length=75)) last_name = TranslatedField( models.CharField(verbose_name=_('last name'), max_length=75)) gender = models.SmallIntegerField(verbose_name=_('I am'), choices=GENDER_CHOICES) date_of_birth = models.DateField(verbose_name=_('date of birth')) # ~~~~ TODO: diet, smoking_status and marital_status - decide which model should contain them - are they relevant also to Speedy Net or only to Speedy Match? diet = models.SmallIntegerField(verbose_name=_('diet'), choices=DIET_CHOICES_WITH_DEFAULT, default=DIET_UNKNOWN) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) access_dob_day_month = UserAccessField( verbose_name=_('who can view my birth month and day'), default=UserAccessField.ACCESS_ME) access_dob_year = UserAccessField( verbose_name=_('who can view my birth year'), default=UserAccessField.ACCESS_ME) notify_on_message = models.PositiveIntegerField( verbose_name=_('on new messages'), choices=NOTIFICATIONS_CHOICES, default=NOTIFICATIONS_ON) objects = UserManager() @property def validators(self): validators = { 'username': get_username_validators( min_username_length=self.settings.MIN_USERNAME_LENGTH, max_username_length=self.settings.MAX_USERNAME_LENGTH, allow_letters_after_digits=False), 'slug': get_slug_validators( min_username_length=self.settings.MIN_USERNAME_LENGTH, max_username_length=self.settings.MAX_USERNAME_LENGTH, min_slug_length=self.settings.MIN_SLUG_LENGTH, max_slug_length=self.settings.MAX_SLUG_LENGTH, allow_letters_after_digits=False) + ["validate_slug"], 'date_of_birth': [validate_date_of_birth_in_model], } return validators @property def email(self): try: return self.email_addresses.get(is_primary=True).email except UserEmailAddress.DoesNotExist: return None @property def profile(self): if (not (hasattr(self, '_profile'))): self.refresh_all_profiles() return self._profile @property def speedy_net_profile(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_speedy_net_profile'))): self.refresh_all_profiles() return self._speedy_net_profile @property def speedy_match_profile(self): if (django_settings.LOGIN_ENABLED): if (not (hasattr(self, '_speedy_match_profile'))): self.refresh_all_profiles() return self._speedy_match_profile class Meta: verbose_name = _('user') verbose_name_plural = _('users') ordering = ('-last_login', 'id') swappable = 'AUTH_USER_MODEL' def __str__(self): # Depends on site: full name in Speedy Net, first name in Speedy Match. return self.profile.get_name() def set_password(self, raw_password): password_validation.validate_password(password=raw_password) return super().set_password(raw_password=raw_password) def delete(self, *args, **kwargs): if ((self.is_staff) or (self.is_superuser)): warnings.warn('Can’t delete staff user.') return False else: return super().delete(*args, **kwargs) def clean_fields(self, exclude=None): """ Allows to have different slug and username validators for Entity and User. """ if exclude is None: exclude = [] if (self.is_superuser): exclude = ['username', 'slug'] return super().clean_fields(exclude=exclude) def clean_all_fields(self, exclude=None): super().clean_all_fields(exclude=exclude) for base_field_name in __class__.LOCALIZABLE_FIELDS: self.clean_localizable_field(base_field_name=base_field_name) def clean_localizable_field(self, base_field_name): # raise Exception(base_field_name)############ # ~~~~ TODO: remove this line! field_names = get_all_field_names(base_field_name=base_field_name) for field_name in field_names: if (not (string_is_not_empty(getattr(self, field_name)))): for _field_name in field_names: # Check again because maybe this field is already not empty. if (not (string_is_not_empty(getattr(self, field_name)))): if (string_is_not_empty(getattr(self, _field_name))): setattr(self, field_name, getattr(self, _field_name)) def get_absolute_url(self): return reverse('profiles:user', kwargs={'slug': self.slug}) def mail_user(self, template_name_prefix, context=None, send_to_unconfirmed=False): addresses = self.email_addresses.filter(is_primary=True) if (not (send_to_unconfirmed)): addresses = addresses.filter(is_confirmed=True) addresses = list(addresses) if (addresses): return addresses[0].mail(template_name_prefix=template_name_prefix, context=context) return False def get_full_name(self): return '{} {}'.format(self.first_name, self.last_name).strip() or self.slug def get_first_name(self): return '{}'.format(self.first_name).strip() or self.slug def get_short_name(self): return self.get_first_name() def has_confirmed_email(self): return (self.email_addresses.filter(is_confirmed=True).exists()) @cached_property def has_confirmed_email_or_registered_now(self): return ((self.has_confirmed_email()) or (self.date_created > now() - timedelta(hours=2))) def activate(self): self.is_active = True self.save_user_and_profile() def get_profile(self, model=None, profile_model=None) -> 'SiteProfileBase': if (model is None): model = get_site_profile_model(profile_model=profile_model) profile = getattr(self, model.RELATED_NAME, None) if (profile is None): profile = model.objects.get_or_create(user=self)[0] return profile def refresh_all_profiles(self): self._profile = self.get_profile() if (django_settings.LOGIN_ENABLED): from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile self._speedy_net_profile = self.get_profile( model=SpeedyNetSiteProfile) self._speedy_match_profile = self.get_profile( model=SpeedyMatchSiteProfile) def save_user_and_profile(self): with transaction.atomic(): self.save() self.profile.save() if (django_settings.LOGIN_ENABLED): self.speedy_net_profile.save() # ~~~~ TODO: is this necessary? self.speedy_match_profile.save( ) # ~~~~ TODO: is this necessary? def get_gender(self): return self.__class__.GENDERS_DICT.get(self.gender) def get_diet(self): diets = { self.__class__.DIET_VEGAN: pgettext_lazy(context=self.get_gender(), message='Vegan'), self.__class__.DIET_VEGETARIAN: pgettext_lazy(context=self.get_gender(), message='Vegetarian'), self.__class__.DIET_CARNIST: pgettext_lazy(context=self.get_gender(), message='Carnist'), } return diets.get(self.diet) def get_age(self): return get_age(date_of_birth=self.date_of_birth) def get_diet_choices(self): return self.__class__.diet_choices(gender=self.get_gender())