Django - Simplify Proxy Model to single class - django

I have User table in my DB, they can be active or inactive. If I only want to query on active user, I define a Proxy Model like following.
class User(models.Model):
name = models.CharField(max_length=50)
location = models.CharField(max_length=50)
active = models.BooleanField()
class UserActive(models.Manager):
def get_queryset(self):
return super(UserActive, self).get_queryset().filter(active=True)
class ActiveUser(User):
objects = UserActive()
class Meta:
proxy = True
Then by working with ActiveUser, I can do my calculation/statistic with only active user.
The problem is, I need to define both UserActive and ActiveUser class, it seems awkward to me. Because with each main class (in this case is User), we need to define two other classes. Imaging we have several other model need to implement Proxy, the code would look messy. May I know if we can have more elegant way ?
Thanks
Alex

I would really avoid overwriting the .objects manager, and use this as some sort of implicit filtering. The Zen of Python is explicit is better than implicit, by using ActiveUser, you basically implement a filtering manager, but propose it like the entire set.
Perhaps a more elegant solution is to define multiple managers. So we can construct a filtering manager decorator:
def filter_manager(**kwargs):
def decorator(klass):
def get_queryset(self):
return super(klass, self).get_queryset().filter(**kwargs)
klass.get_queryset = get_queryset
return klass
return decorator
This decorator will however throw away a get_queryset that is defined on the manager itself, so you can not perform an extra patch with this.
Now we can define some managers in a rather elegant way:
#filter_manager(active=True)
class ActiveManager(models.Manager):
pass
#filter_manager(active=False)
class InactiveManager(models.Manager):
pass
Finally we can add these managers to the User model, and use explicit names:
class User(models.Model):
name = models.CharField(max_length=50)
location = models.CharField(max_length=50)
active = models.BooleanField()
objects = models.Manager()
active_users = ActiveManager()
inactive_users = InactiveManager()
So now we can use User.active_users to query for the active users. We thus have no proxy models, and can query with User.active_users.count() for example (well we can perform all operations like with .objects but then for .active_users.

I created a new Django project, with only User model. My models.py look like this
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.
def filter_manager(**kwargs):
def decorator(klass):
def get_queryset(self):
return super(klass, self).get_queryset().filter(**kwargs)
klass.get_queryset = get_queryset
return klass
return decorator
#filter_manager(active=True)
class ActiveManager(models.Manager):
pass
#filter_manager(active=False)
class InactiveManager(models.Manager):
pass
class User(models.Model):
name = models.CharField(max_length=50)
location = models.CharField(max_length=50)
active = models.BooleanField()
active_user = ActiveManager()
When I tried User.objects.all().
Error: type object 'User' has no attribute 'objects'

Related

How to access custom QuerySet methods from the Manager of a ForeignKey

I'm using Django Managers to make a higher API to interact with my database and keeping my code cleaner and more readable. But in case I have a Foreignkey relationship, I can't use the manager of the ForeignKey model. The queries are more complex as below, but I just simplified the example so, It can be easier to read and get the idea of the question:
models.py:
class Community(models.Model):
objects = CommunityManager()
...
class Inscription(models.Model):
objects = InscriptionManager()
...
community = models.ForeignKey("Community", related_name="inscriptions")
created_at = models.DateTimeField()
managers.py:
from datetime import date
from django.db import models
class InscriptionQuerySet(models.query.QuerySet):
def by_day(self, day=date.today()):
return self.filter(created_at__day=day)
... # more queries
class InscriptionManager(models.Manager):
def get_query_set(self):
return InscriptionQuerySet(self.model, using=self._db)
def today(self):
return self.get_query_set().by_day()
... # more queries
class CommunityQuerySet(models.query.QuerySet):
def by_type(self, type):
return self.filter(type=type)
... # more queries
class CommunityManager(models.Manager):
def get_query_set(self):
return OrganistaionQuerySet(self.model, using=self._db)
def by_type(self, type):
return self.get_query_set().by_type(type)
... # more queries
Usage:
Inscription.objects.by_day() # return correctly all the inscriptions made today
Community.objects.by_type('type1') # return correctly all Communities that match
Problem: but here lies the problem
community_b = Community.objects.get(id=12)
community_b.inscriptions.by_day()
>>> AttributeError: 'ForeignRelatedObjectsDescriptor' object has no attribute 'by_day'
How can I fixe this. How to customize the manager to take in consideration the models relation.
I don't see the need of both Manager and QuerySet in your approach. You could just do away with a QuerySet. This is how I generally implement custom managers, and I tried accessing custom manager method on reverse relationship in one of my projects, and it works fine:
class InscriptionQuerySet(models.QuerySet):
def by_day(self, day=date.today()):
return self.filter(created_at__day=day)
def today(self):
return self.by_day()
class CommunityQuerySet(models.QuerySet):
def by_type(self, type):
return self.filter(type=type)
And then in your models, change your objects like this:
class Community(models.Model):
objects = CommunityQuerySet.as_manager()
...
class Inscription(models.Model):
objects = InscriptionQuerySet.as_manager()
I think you would be able to access custom queryset method with this setup.

Django Rest Framework Update or Create if not exists

I want to use the perform an update or create in django-rest-framework, by passing or not the id field. I've got this model
class Etiqueta(models.Model):
name_tag = models.CharField(max_length=200, blank=False, null=False)
description_tag = models.TextField(max_length=500, blank=False, null=False)
def __unicode__(self):
return self.name_tag
And in django-rest-framework I've got this serializer
from myapp.modulos.estado_1.models import Etiqueta
from rest_framework import serializers, viewsets
# Serializers define the API representation.
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Etiqueta
fields = (
'id',
'name_tag',
'description_tag'
)
# ViewSets define the view behavior.
class TagViewSet(viewsets.ModelViewSet):
queryset = Etiqueta.objects.all()
serializer_class = TagSerializer
Normally when I create an object, I perform a POST to the URL without the /:id, but if I've got an object with a local id, I want him to be created in the REST with the same id (remote id), django overwrite my local id and creates a new one. Does anybody know how achieve this? Also it is important to mention that I'm working with google-app-engine, google-cloud-datastore and django-dbindexer.
This code should work for your case -
class TagViewSet(viewsets.ModelViewSet):
queryset = Etiqueta.objects.all()
serializer_class = TagSerializer
def get_object(self):
if self.request.method == 'PUT':
obj, created = Etiquetta.objects.get_or_create(pk=self.kwargs.get('pk'))
return obj
else:
return super(TagViewSet, self).get_object()
You should have a look at how Django REST framework does currently and adapts your create method to update whenever you have an id field.
The original ViewSet.create is here and the ViewSet.update is here.
Please note that you will probably end up with two different serializers for /tag/ and /tag/:id since the later should not allow the id field to be writable while the former should.
I've write a drf views mixin for updating an object by id, if no corresponding object, just create it then update.

how to handle multiple profiles per user?

I'm doing something that doesn't feel very efficient. From my code below, you can probably see that I'm trying to allow for multiple profiles of different types attached to my custom user object (Person). One of those profiles will be considered a default and should have an accessor from the Person class. Storing an is_default field on the profile doesn't seem like it would be the best way to keep track of a default, is it?
from django.db import models
from django.contrib.auth.models import User, UserManager
class Person(User):
public_name = models.CharField(max_length=24, default="Mr. T")
objects = UserManager()
def save(self):
self.set_password(self.password)
super(Person, self).save()
def _getDefaultProfile(self):
def_teacher = self.teacher_set.filter(default=True)
if def_teacher: return def_teacher[0]
def_student = self.student_set.filter(default=True)
if def_student: return def_student[0]
def_parent = self.parent_set.filter(default=True)
if def_parent: return def_parent[0]
return False
profile = property(_getDefaultProfile)
def _getProfiles(self):
# Inefficient use of QuerySet here. Tolerated because the QuerySets should be very small.
profiles = []
if self.teacher_set.count(): profiles.append(list(self.teacher_set.all()))
if self.student_set.count(): profiles.append(list(self.student_set.all()))
if self.parent_set.count(): profiles.append(list(self.parent_set.all()))
return profiles
profiles = property(_getProfiles)
class BaseProfile(models.Model):
person = models.ForeignKey(Person)
is_default = models.BooleanField(default=False)
class Meta:
abstract = True
class Teacher(BaseProfile):
user_type = models.CharField(max_length=7, default="teacher")
class Student(BaseProfile):
user_type = models.CharField(max_length=7, default="student")
class Parent(BaseProfile):
user_type = models.CharField(max_length=7, default="parent")
First of all you could make things a lot more easy by not declaring the BaseProfile abstract:
from django.db import models
from django.contrib.auth.models import User, UserManager
class Person(User):
public_name = models.CharField(max_length=24, default="Mr. T")
objects = UserManager()
def save(self):
self.set_password(self.password)
super(Person, self).save()
def _getDefaultProfile(self):
try:
return self.baseprofile_set.get(default=True)
except ObjectDoesNotExist:
return False
profile = property(_getDefaultProfile)
def _getProfiles(self):
return self.baseprofile_set.all()
profiles = property(_getProfiles)
class BaseProfile(models.Model):
person = models.ForeignKey(Person)
is_default = models.BooleanField(default=False)
class Teacher(BaseProfile):
user_type = models.CharField(max_length=7, default="teacher")
class Student(BaseProfile):
user_type = models.CharField(max_length=7, default="student")
class Parent(BaseProfile):
user_type = models.CharField(max_length=7, default="parent")
The way this is nicer? Your properties didn't know anyway what type they were returning, so the abstract baseclass only made you have an incredible annoying overhead there.
If you now are wondering how the hell you can get the data from the specific profiles since I made anything returned BaseProfile? You can do something like this:
try:
#note the lowercase teacher referal
print myuser.profile.teacher.someteacherfield
except Teacher.DoesNotExist:
print "this is not a teacher object!"
Also I do hope you didn't use the user_type field solely for this purpose, because django has it built in better as you can see. I also hope you really have some other unique fields in your derived profile classes because otherwise you should throw them away and just past a usertype field into BaseProfile (look at choices to do this good).
Now as for the is_default, imho this method is as good as any. You can always try to add custom constraints to your dbms itself, saying there sould be 0 or 1 records containing the same FK and is_default=True (there is no django way to do this). What I also would say is, add a method make_default and in that method make sure the is_default is unique for that person (e.g. by first setting is_default to False on all profiles with the same FK). This will save you a lot of possible sorrow. You can also add this check in the save() method of BaseProfile.
Another way you could do it is by adding a Foreign Key to the Person Model that points to the default Profile. While this will ensure default to be unique on django level, it can also provide denormalization and corruption of your data, even on a more annoying level, so I'm no big fan of it. But again, if you do all adding/removing/updating of profiles through predefined methods (will be more complex now!) you should be safe.
Finally, maybe you have good reasons to inherit from User, but the default way to extend the User functionality is not this, it's described here.

Custom QuerySet and Manager without breaking DRY?

I'm trying to find a way to implement both a custom QuerySet and a custom Manager without breaking DRY. This is what I have so far:
class MyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()
This works fine, until I do something like this:
inquiries = Inquiry.objects.filter(status=some_status)
my_inquiry_count = inquiries.for_user(request.user).count()
This promptly breaks everything because the QuerySet doesn't have the same methods as the Manager. I've tried creating a custom QuerySet class, and implementing it in MyInquiryManager, but I end up replicating all of my method definitions.
I also found this snippet which works, but I need to pass in the extra argument to for_user so it breaks down because it relies heavily on redefining get_query_set.
Is there a way to do this without redefining all of my methods in both the QuerySet and the Manager subclasses?
The Django 1.7 released a new and simple way to create combined queryset and model manager:
class InquiryQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
objects = InqueryQuerySet.as_manager()
See Creating Manager with QuerySet methods for more details.
Django has changed! Before using the code in this answer, which was written in 2009, be sure to check out the rest of the answers and the Django documentation to see if there is a more appropriate solution.
The way I've implemented this is by adding the actual get_active_for_account as a method of a custom QuerySet. Then, to make it work off the manager, you can simply trap the __getattr__ and return it accordingly
To make this pattern re-usable, I've extracted out the Manager bits to a separate model manager:
custom_queryset/models.py
from django.db import models
from django.db.models.query import QuerySet
class CustomQuerySetManager(models.Manager):
"""A re-usable Manager to access a custom QuerySet"""
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
# don't delegate internal methods to the queryset
if attr.startswith('__') and attr.endswith('__'):
raise
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model, using=self._db)
Once you've got that, on your models all you need to do is define a QuerySet as a custom inner class and set the manager to your custom manager:
your_app/models.py
from custom_queryset.models import CustomQuerySetManager
from django.db.models.query import QuerySet
class Inquiry(models.Model):
objects = CustomQuerySetManager()
class QuerySet(QuerySet):
def active_for_account(self, account, *args, **kwargs):
return self.filter(account=account, deleted=False, *args, **kwargs)
With this pattern, any of these will work:
>>> Inquiry.objects.active_for_account(user)
>>> Inquiry.objects.all().active_for_account(user)
>>> Inquiry.objects.filter(first_name='John').active_for_account(user)
UPD if you are using it with custom user(AbstractUser), you need to change
from
class CustomQuerySetManager(models.Manager):
to
from django.contrib.auth.models import UserManager
class CustomQuerySetManager(UserManager):
***
You can provide the methods on the manager and queryset using a mixin.
This also avoids the use of a __getattr__() approach.
from django.db.models.query import QuerySet
class PostMixin(object):
def by_author(self, user):
return self.filter(user=user)
def published(self):
return self.filter(published__lte=datetime.now())
class PostQuerySet(QuerySet, PostMixin):
pass
class PostManager(models.Manager, PostMixin):
def get_query_set(self):
return PostQuerySet(self.model, using=self._db)
You can now use the from_queryset() method on you manager to change its base Queryset.
This allows you to define your Queryset methods and your manager methods only once
from the docs
For advanced usage you might want both a custom Manager and a custom QuerySet. You can do that by calling Manager.from_queryset() which returns a subclass of your base Manager with a copy of the custom QuerySet methods:
class InqueryQueryset(models.Queryset):
def custom_method(self):
""" available on all default querysets"""
class BaseMyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
MyInquiryManager = BaseInquiryManager.from_queryset(InquiryQueryset)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()
A slightly improved version of T. Stone’s approach:
def objects_extra(mixin_class):
class MixinManager(models.Manager, mixin_class):
class MixinQuerySet(QuerySet, mixin_class):
pass
def get_query_set(self):
return self.MixinQuerySet(self.model, using=self._db)
return MixinManager()
Class decorators make usage as simple as:
class SomeModel(models.Model):
...
#objects_extra
class objects:
def filter_by_something_complex(self, whatever parameters):
return self.extra(...)
...
Update: support for nonstandard Manager and QuerySet base classes, e. g. #objects_extra(django.contrib.gis.db.models.GeoManager, django.contrib.gis.db.models.query.GeoQuerySet):
def objects_extra(Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet):
def oe_inner(Mixin, Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet):
class MixinManager(Manager, Mixin):
class MixinQuerySet(QuerySet, Mixin):
pass
def get_query_set(self):
return self.MixinQuerySet(self.model, using=self._db)
return MixinManager()
if issubclass(Manager, django.db.models.Manager):
return lambda Mixin: oe_inner(Mixin, Manager, QuerySet)
else:
return oe_inner(Mixin=Manager)
based on django 3.1.3 source code, i found a simple solution
from django.db.models.manager import BaseManager
class MyQuerySet(models.query.QuerySet):
def my_custom_query(self):
return self.filter(...)
class MyManager(BaseManager.from_queryset(MyQuerySet)):
...
class MyModel(models.Model):
objects = MyManager()
There are use-cases where we need to call custom QuerySet methods from the manager instead of using the get_manager method of a QuerySet.
A mixin would suffice based on the solution posted in one of the accepted solution comments.
class CustomQuerySetManagerMixin:
"""
Allow Manager which uses custom queryset to access queryset methods directly.
"""
def __getattr__(self, name):
# don't delegate internal methods to queryset
# NOTE: without this, Manager._copy_to_model will end up calling
# __getstate__ on the *queryset* which causes the qs (as `all()`)
# to evaluate itself as if it was being pickled (`len(self)`)
if name.startswith('__'):
raise AttributeError
return getattr(self.get_queryset(), name)
For example,
class BookQuerySet(models.QuerySet):
def published(self):
return self.filter(published=True)
def fiction(self):
return self.filter(genre="fiction")
def non_fiction(self):
return self.filter(genre="non-fiction")
class BookManager(CustomQuerySetManagerMixin, models.Manager):
def get_queryset(self):
return BookQuerySet(self.model, using=self._db).published()
class Book(models.Model):
title = models.CharField(max_length=200)
genre = models.CharField(choices=[('fiction', _('Fiction')), ('non-fiction', _('Non-Fiction'))])
published = models.BooleanField(default=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
objects = BookManager()
class Author(models.Model):
name = models.CharField(max_length=200)
With the above, we can access related objects (Book) like below without defining new methods in the manager for each queryset method.
fiction_books = author.books.fiction()
The following works for me.
def get_active_for_account(self,account,*args,**kwargs):
"""Returns a queryset that is
Not deleted
For the specified account
"""
return self.filter(account = account,deleted=False,*args,**kwargs)
This is on the default manager; so I used to do something like:
Model.objects.get_active_for_account(account).filter()
But there is no reason it should not work for a secondary manager.

Dynamic Meta attributes for Django Models?

I am trying to add a dynamic Meta attribute to all of my Django models using model inheritance, but I can't get it to work. I have a permission that I want to add to all my models like this:
class ModelA(models.Model):
class Meta:
permisssions =(('view_modela','Can view Model A'),)
class ModelB(models.Model):
class Meta:
permisssions =(('view_modelb','Can view Model B'),)
I tried creating an abstract base class like this:
class CustomModel(models.Model):
def __init__(self, *args, **kwargs):
self._meta.permissions.append(('view_'+self._meta.module_name, u'Can view %s' % self._meta.verbose_name))
super(CustomModel,self).__init__(*args, **kwargs)
class ModelA(CustomModel):
....
class ModelB(CustomModel):
...
but it's not working. Is this the right approach? Because Django uses introspection to construct the Model classes, I'm not sure if adding permissions during the __init__() of the class will even work. With my current implementation every time I access a model instance it appends another tuple of the permissions.
Your instinct is right that this won't work. In Django, permissions are stored in the database, which means that:
they need to be available at the class level when syncdb is run in order to populate the auth_permission table (and your approach requires an instance, which won't be made during syncdb)
even if you did add it to _meta.permissions in __init__, the User object wouldn't pick it up in any permission check calls because those consult the permissions table in the DB (and a cache of that table, at that).
Your goal can't be accomplished using inheritance. What you actually need here is a Python metaclass.
This metaclass re-writes your ModelA and ModelB class definitions dynamically before they are defined, thus it doesn't require a ModelA instance, and is available to syncdb. Since Django's models also use metaclasses to build the Meta object in the first place, the only requirement is that your metaclass must inherit from the same metaclass as Django's models.
Here's some sample code (Python 2):
from django.db.models.base import ModelBase
class CustomModelMetaClass(ModelBase):
def __new__(cls, name, bases, attrs):
klas = super(CustomModelMetaClass, cls).__new__(cls, name, bases, attrs)
klas._meta.permissions.append(
(
'view_{0.module_name}'.format(klas._meta),
u'Can view {0.verbose_name}'.format(klas._meta))
)
return klas
class ModelA(models.Model):
__metaclass__ = CustomModelMetaClass
test = models.CharField(max_length=5)
Python 3:
from django.db.models.base import ModelBase
class CustomModelMetaClass(ModelBase):
def __new__(cls, name, bases, attrs):
klas = super().__new__(cls, name, bases, attrs)
klas._meta.permissions.append(
(
'view_{0.module_name}'.format(klas._meta),
'Can view {0.verbose_name}'.format(klas._meta))
)
return klas
class ModelA(models.Model, metaclass=CustomModelMetaClass):
test = models.CharField(max_length=5)
Note that permissions in this case will be written only on migrate. If you need to change permissions dynamically at run time base on the user, you'll want to provide your own authentication backend.
Try to use a custom manager:
#create a custom manager
class DynTableNameManager(models.Manager):
#overwrite all() (example)
#provide table_name
def all(self, table_name):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
SELECT id, name
FROM %s
""" % table_name)
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], name=row[1])
result_list.append(p)
return result_list
#cerate a dummy table
class DummyTable(models.Model):
name = models.CharField ( max_length = 200 )
objects = DynTableNameManager()
use like this:
f = DummyTable.objects.all('my_table_name')