I want to update my model upon login (to check the authorizations of a person from an external system).
The code of my model looks as follow:
import json
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.signals import user_logged_in
from django.db import models
class Person(AbstractUser):
is_dean = models.BooleanField(null=False, blank=False, default=False)
is_institute_manager = models.BooleanField(null=False, blank=False, default=False)
managed_institutes = models.TextField(blank=True, null=True, default=None)
def get_managed_institutes(self):
return json.loads(self.managed_institutes)
def set_managed_institutes(self, value):
self.managed_institutes = json.dumps(value)
# Signals processing
def check_authorizations(sender, user, request, **kwargs):
...
# check if the user is dean
is_dean = False
# logic to check if the user is dean...
user.is_dean = is_dean
# Check if the user manages institutes
is_institute_manager = False
managed_institutes = list()
# Logic to check if the user is managing institutes ...
user.is_institute_manager = is_institute_manager
user.set_managed_institutes = managed_institutes
user.save()
user_logged_in.connect(check_authorizations)
Surprisingly, the boolean flags get set correctly, but the method set_managed_institute never gets called...
I am quite convinced this a trivial mistake from my end, but I can't figure it out.
That is not how you call methods in Python. You need to do so explicitly:
user.set_managed_institutes(managed_institutes)
Or did you mean to define a property?
#property
def managed_institutes(self):
return json.loads(self._managed_institutes)
#managed_institutes.setter
def managed_institutes(self, value):
self._managed_institutes = json.dumps(value)
But also note, you probably want to use a JsonField anyway. If you're using PostgreSQL, there is one defined in Django directly; otherwise there are several third-party libraries that take care of serializing and deserializing your data on load/save.
Related
I'd like to have a Django model with a reserved field, so that no one can set it directly but its value it's generated at saving time. This is useful for example to generate user tokens and I want to prevent developers to directly set a value for the token key. At the same time I would like to be able to treat that field as I do with others, so using __ for fields lookup in queries, or be able to retrieve tokens as:
token = Token.objects.get(key='c331054c00494f6a22f0ebde7a32bf9d4619b988')
So in my mind doing something like:
Token.key = 'my-token-key'
should fail, and even instantiation should fail:
token = Token(key='my-token-key')
So far I came up with this solution, but I'm a bit concerned my changes could break some Django workflow since I'm not sure what my changes will affect:
import binascii
import datetime
import os
from django.contrib.auth import get_user_model
from django.db import models
class Token(models.Model):
"""
An access token that is associated with a user.
"""
id = models.AutoField(primary_key=True)
# By default `get_attname` returns the field `name`,
# but in my case the attribute name is different
_key = models.CharField(max_length=40, unique=True, name='key', db_column='key')
_key.get_attname = lambda: '_key'
name = models.CharField(max_length=255)
user = models.ForeignKey(get_user_model(), related_name='tokens')
created = models.DateTimeField(auto_now_add=True)
last_access_time = models.DateTimeField(null=True, blank=True)
expires = models.DateField(
null=True,
blank=True,
help_text="Leave empty for non-expiring tokens. "
"Once the token has expired you can not extend its validity.",
)
#property
def key(self):
return self._key
#key.setter
def key(self, value):
raise ValueError("Can not set key directly. It is automatically generated when saving the model.")
def save(self, *args, **kwargs):
if not self._key:
self._key = self._generate_key()
super(Token, self).save(*args, **kwargs)
#staticmethod
def _generate_key():
return binascii.hexlify(os.urandom(20)).decode()
#property
def expired(self):
return bool(self.expires and self.expires < datetime.date.today())
def __str__(self):
return '{} - {}'.format(self.user, self.name)
class Meta:
verbose_name = "User Token"
verbose_name_plural = "User Tokens"
unique_together = (('name', 'user'),)
As you can see I tried overriding the get_attname method of the key field (needed because the field name and the property are the same and it would lead to errors loading forms). This seems to work just fine, but I would like to know if this could lead to problems running queries.
Maybe there is a simpler way to do this but I couldn't find anything better.
P.S.: I'm using python2 with Django 1.11
Thanks a lot to everyone!
Why am I getting an issue when calling the get_model() function? Here is what I am trying to do:
#classmethod
def get_content_models(cls):
"""
Return all Package subclasses.
"""
is_content_model = lambda m: m is not Package and issubclass(m, Package)
return list(filter(is_content_model, models.get_models()))
This used to work before, but now after updating to the new Django, it's throwing an error. How can this be resolved?
UPDATE
Below is my model
from django.db import models
class Package(BasePackage):
"""
A package in the package tree. This is the base class that custom content types
need to subclass.
"""
parent = models.ForeignKey("Package", blank=True, null=True, related_name="children", on_delete=models.CASCADE)
titles = models.CharField(editable=False, max_length=1000, null=True)
content_model = models.CharField(editable=False, max_length=50, null=True)
in_menus = MenusField(_("Show in menus"), blank=True, null=True)
login_required = models.BooleanField(_("Login required"), default=False,
help_text=_("If checked, only logged in users can view this Package"))
itinerary = models.ManyToManyField('ItineraryItem', through="PackageItinerary")
def __str__(self):
return self.title
def save(self, *args, **kwargs):
"""
Create the titles field using the titles up the parent chain
and set the initial value for ordering.
"""
if self.id is None:
self.content_model = self._meta.object_name.lower()
self.titles = self.title
super(Package, self).save(*args, **kwargs)
#classmethod
def get_content_models(cls):
"""
Return all Package subclasses.
"""
is_content_model = lambda m: m is not Package and issubclass(m, Package)
return list(filter(is_content_model, models.get_models()))
def get_content_model(self):
"""
Provies a generic method of retrieving the instance of the custom
content type's model for this Package.
"""
return getattr(self, self.content_model, None)
It is an AttributeError owing to the fact that models.get_model() was removed in Dango 1.9.
You are supposed to use dango.apps.apps.get_model().
Some discussion is here and here
Here is how you use it now.
from django.apps import apps
MyModel = apps.get_model('app name where the model is','name of the model you want to get from that app')
# Do your logic here with MyModel
However, if all you want is to get model, why not import it straight away? How you are using the code downstream? Please note that due to change (from 1.9 onwards) the properties of function might have changed. Thus you may want to consider latest module and functions to achieve your results (that you previously used to get). This means more work for you to come in sync with later versions of Django but you might run into problems anyways due to the change in get_model.
In summary, see what the code is doing and adapt to newer versions of Django.
I am not sure if I helped you or confused you. Sorry if I did the later.
Best wishes.
from django.apps import apps
ModelClass = apps.get_model('app_name.ModelClass')
You can now instatiate this class
mc = ModelClass()
Doc here
I am writing tests for a large Django application, as part of this process I am gradually creating factories for all models of the different apps within the Django project.
However, I've run into some confusing behavior with FactoryBoy where it almost seems like SubFactories have an max depth beyond which no instances are generated.
The error occurs when I try to run the following test:
def test_subfactories(self):
""" Verify that the factory is able to initialize """
user = UserFactory()
self.assertTrue(user)
self.assertTrue(user.profile)
self.assertTrue(user.profile.tenant)
order = OrderFactory()
self.assertTrue(order)
self.assertTrue(order.user.profile.tenant)
The last line will fail (AssertionError: None is not true), running this test through a debugger reveals that indeed order.user.profile.tenant returns None instead of the expected Tenant instance.
There are quite a few factories / models involved here, but the layout is relatively simple.
The User (django default) and the Profile model are linked through a OneToOneField, which (after some trouble) is represented by the UserFactory and ProfileFactory
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = yuza_models.Profile
django_get_or_create = ('user',)
user = factory.SubFactory('yuza.factories.UserFactory')
birth_date = factory.Faker('date_of_birth')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
city = factory.Faker('city')
country = factory.Faker('country')
avatar_file = factory.django.ImageField(color='blue')
tenant = factory.SubFactory(TenantFactory)
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Sequence(lambda n: "user_%d" % n)
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')
is_staff = False
is_superuser = False
is_active = True
last_login = factory.LazyFunction(timezone.now)
#factory.post_generation
def profile(self, create, extracted):
if not create:
return
if extracted is None:
ProfileFactory(user=self)
The TenantFactory below is represented as a SubFactory on the ProfileFactory above.
class TenantFactory(factory.django.DjangoModelFactory):
class Meta:
model = elearning_models.Tenant
name = factory.Faker('company')
slug = factory.LazyAttribute(lambda obj: text.slugify(obj.name))
name_manager = factory.Faker('name')
title_manager = factory.Faker('job')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
house_number_addition = factory.Faker('secondary_address')
The Order is linked to a User, but many of its methods call fields of its self.user.profile.tenant
class OrderFactory(factory.DjangoModelFactory):
class Meta:
model = Order
user = factory.SubFactory(UserFactory)
order_date = factory.LazyFunction(timezone.now)
price = factory.LazyFunction(lambda: Decimal(random.uniform(1, 100)))
site_tenant = factory.SubFactory(TenantFactory)
no_tax = fuzzy.FuzzyChoice([True, False])
Again, most of the asserts in the test pass without failing, all separate factories are able to initialize fetch values from their immediate foreignkey relations. However, as soon as factories/models are three steps removed from each other the call will return None instead of the expected Tenant instance.
Since I was unable to find any reference to this behaviour in the FactoryBoy documentation its probably a bug on my side, but so far I've been unable to determine its origin. Does anyone know what I am doing wrong?
post_save method
def create_user_profile(sender, instance, created, **kwargs):
if created:
profile = Profile.objects.create(user=instance)
resume = profile.get_resume()
resume.initialize()
post_save.connect(create_user_profile, sender=User)
As I mentioned in a comment, I've discovered the source of the problem: the post-save method linked to the UserProfile (I've included the code in my post).
This post-save method created a Profile on User creation. I accounted for this signal by using the #factory.django.mute_signals decorater on both the UserFactoryand the ProfileFactory.
I had assumed that any calls on Order.user would trigger the UserFactory which had already been enclosed with the decorator, but this is not assumption proved to be wrong. Only when I applied the decorated to the OrderFactory as well did the tests pass.
Thus the #factory.django.mute_signals decorator should not just be used on factories that are affected by these signals, but also on any factory that is using those factories as a SubFactory!
Now that Django supports the DateRangeField, is there a 'Pythonic' way to prevent records from having overlapping date ranges?
Hypothetical use case
One hypothetical use case would be a booking system, where you don't want people to book the same resource at the same time.
Hypothetical example code
class Booking(models.model):
# The resource to be reserved
resource = models.ForeignKey('Resource')
# When to reserve the resource
date_range = models.DateRangeField()
class Meta:
unique_together = ('resource', 'date_range',)
I know that the answer is old, but now you can just create a constraint in the meta of the model, that will make Postgres handle this
from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
from django.db import models
from django.db.models import Q
class Room(models.Model):
number = models.IntegerField()
class Reservation(models.Model):
room = models.ForeignKey('Room', on_delete=models.CASCADE)
timespan = DateTimeRangeField()
cancelled = models.BooleanField(default=False)
class Meta:
constraints = [
ExclusionConstraint(
name='exclude_overlapping_reservations',
expressions=[
('timespan', RangeOperators.OVERLAPS),
('room', RangeOperators.EQUAL),
],
condition=Q(cancelled=False),
),
]
Postgress Coonstraints
You can check this in your model full_clean method, which is called automatically during ModelForm validation. It is NOT called automatically if you directly save the object.. this is a known problem with Django validation that you may be aware of already! So if you want validation any time the object is saved, you have to also override the model save method.
class Booking(models.model):
def full_clean(self, *args, **kwargs):
super(Booking, self).full_clean(*args, **kwargs)
o = Booking.objects.filter(date_range__overlap=self.date_range).exclude(pk=self.pk).first()
if o:
raise forms.ValidationError('Date Range overlaps with "%s"' % o)
# do not need to do this if you are only saving the object via a ModelForm, since the ModelForm calls FullClean.
def save(self):
self.full_clean()
super(Booking, self).save()
What's the difference between pinax.apps.accounts and the idios profiles app that were installed with the profiles base project?
As I understand it, the contrib.auth should be just for authentication purpose (i.e. username and password), and the existence of User.names and User.email in the auth model is historical and those fields shouldn't be used; but the distinction between accounts and profiles are lost to me. Why is there pinax.apps.account and idios?
The Pinax account is just a wrapper for that holds the user, timezone and language. user is a foreign key relation to the standard django.auth User model.
class Account(models.Model):
user = models.ForeignKey(User, unique=True, verbose_name=_('user'))
timezone = TimeZoneField(_('timezone'))
language = models.CharField(_('language'), max_length=10, choices=settings.LANGUAGES, default=settings.LANGUAGE_CODE)
def __unicode__(self):
return self.user.username
The idios Profile model basically does the same thing but has some custom methods:
class ProfileBase(models.Model):
# ### could be unique=True if subclasses don't inherit a concrete base class
# ### need to look at this more
user = models.ForeignKey(User, verbose_name=_("user"))
class Meta:
verbose_name = _("profile")
verbose_name_plural = _("profiles")
abstract = True
def __unicode__(self):
return self.user.username
def get_absolute_url(self):
if idios.settings.MULTIPLE_PROFILES:
# ### using PK here is kind of ugly. the alternative is to
# generate a unique slug for each profile, which is tricky
kwargs = {
"profile_slug": self.profile_slug,
"pk": self.pk
}
else:
if idios.settings.USE_USERNAME:
kwargs = {"username": self.user.username}
else:
kwargs = {"pk": self.pk}
return reverse("profile_detail", kwargs=kwargs)
#classmethod
def get_form(cls):
return get_profile_form(cls)
def _default_profile_slug(cls):
return cls._meta.module_name
profile_slug = ClassProperty(classmethod(_default_profile_slug))
Neither of them replicates the authentication functionality of django.auth.User if that is what you are asking. It doesn't look like either one has a dependency on the other either. So if you can't see a good use for both of them, just go with the one that makes sense.
Profiles are meant to be used for public data, or data you'd share with other people and is also more descriptive in nature.
Account data are more like settings for you account that drive certain behavior (language or timezone settings) that are private to you and that control how various aspects of the site (or other apps) function.