Dynamic Meta attributes for Django Models? - django

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

Related

Django - Simplify Proxy Model to single class

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'

Python / django.db: How to create a generic class without hardcoded Meta.db_table?

I have a class using django.db as follows:
from django.db import models
class myClass(models.Model):
"My class"
class Meta:
db_table = 'my_table'
field1 = models.CharField()
# more fields here
def some_function(self):
return "hello"
I'd like to turn this into a generic class that does not have "my_table" hardcoded, so that I can use it in some way such as:
class GenericClass(models.Model):
class Meta:
db_table = None
# genericClass properties and methods here
class myClass(GenericClass):
id = models.CharField(max_length=20)
def __init__(self, *args, **kwargs):
super(self.__class__, self).Meta.db_table = 'my_table'
super(self.__class__, self).__init__(*args, **kwargs)
However, this gives me the following error (using Python 2.7 / Django 1.11):
Local field u'id' in class 'myClass' clashes with field of similar name from base class 'GenericClass'
Is there any way to create a generic class that extends django.db.models.Model, that supports a non-default database table name without hardcoding Meta.db_table?
I guess what you are asking is a Django equivalent of AbstractSuperClass / SuperClass.
If what you want is AbstractSuperClass for django model you can define it like
class AbstractModel(models.Model):
field1 = ...
field2 = ...
...
class Meta:
abstract = True
class SubClassModel(models.Model):
field3 = ...
class Meta:
db_table=sub_class_db_table
Your AbstractModel won't have a database table. It simply acts as a superclass to store common fields (and even you can define common functions).
If you DESC your sub_class_db_table, you will find field1, field2, field3.
See this for more details.
If you want to create a db_table hierarchy itself (field1 and field2 will be in the super_class_table and field3 will be in sub_class_table), you can use Multi-table inheritance. This will be useful especially if there is a service-contract scenario - A team or a module owns common functionality including the db_table, its maintenance. But this way is less commonly used.

Django - add model field validator in __init__

I need to add a field validator through an abstract class.
The code I'm working with supposes that every class inheriting from this abstract class has a field name, but this field itself isn't defined in the abstract class unfortunately.
So I tried the following, but I'm not sure what is the good way of finding the field in self._meta.fields, since this is a list..??
class AbsClass(models.Model):
class Meta:
abstract = True
def __init__(self, *args, **kw):
super(AbsClass, self).__init__(*args, **kw)
name_field = [f for f in self._meta.fields if f.name == 'name'][0] # Is there another way?
name_field.validators.append(my_validator)
You need Options.get_field:
...
name_field = self._meta.get_field('name')
name_field.validators.append(my_validator)
...
Your approach, however, doesn't seem like a good idea: you model's field instances are shared between all instances of your model (their references are stored in a class attribute, not in instance attributes). This means that every time you instantiate an object of your model, you'll be adding another copy of my_validator to the field's validators, because you're adding it to the same field instance.
You could implement a metaclass for your abstract base class and add the validator at compile time instead of tampering with field instances at runtime, something along the lines of this (not tested):
from django.utils.six import with_metaclass
from django.db.models.base import ModelBase
# inherit from Django's model metaclass
class AbsClassMeta(ModelBase):
def __new__(cls, name, bases, attrs):
if 'name' in attrs:
attrs['name'].validators.append(my_validator)
elif name != 'AbsClass':
# is it an error to not have a "name" field in your subclasses?
# handle situation appropriately
return super(AbsClassMeta, cls).__new__(cls, name, bases, attrs)
class AbsClass(with_metaclass(AbsClassMeta, models.Model)):
...
Note that your AbsClass class itself will also be created using this metaclass. If you decide to throw an exception in AbsClassMeta.__new__ if the class doesn't have a name field, you need to take this into account, since your AbsClass class doesn't have a name field.
In Django 2.x
instead of
name_field = self._meta.get_field('name')
name_field.validators.append(my_validator)
you can do:
name_field = self.fields['name']
name_field.validators.append(my_validator)

How to set default values to extended model in Django?

In a Mezzanine project, I have defined a simple Photo model that extends the core Page model:
from django.db import models
from mezzanine.pages.models import Page
class Photo(Page):
image = models.ImageField(upload_to="photos")
#publish_date = models.DateTimeField(auto_now_add=True, blank=True) #problematic
When I tried to re-define the publish_date field, I got error:
django.core.exceptions.FieldError: Local field 'publish_date' in class 'Photo' clashes with field of similar name from base class 'Page'
I want to avoid filling publish_date in admin page each time I create a photo. So wondering how should I set it it to now() without touching the original Page model?
You can't change the definition of a field in a derived class of a model -- what if the base class relies on the existing behavior in any way?
What I'd suggest is define a custom save() method in your Photo class that adds the date, then calls the super() save:
import datetime
def save(self, *args, **kwargs):
if not self.pk:
# instance is being created.
self.publish_date = datetime.datetime.now()
super(Photo, self).save(*args, **kwargs)
If you find yourself doing this a lot, you could create a mixin that adds this functionality to any class.

Django: Inherit Permissions from abstract models?

Is it possible to inherit permissions from an abstract model in Django?
I can not really find anything about that. For me this doesn't work!
class PublishBase(models.Model):
class Meta:
abstract = True
get_latest_by = 'created'
permissions = (('change_foreign_items',
"Can change other user's items"),)
EDIT: Not working means it fails silently. Permission is not created, as it wouldn't exist on the models inheriting from this class.
The permissions are not inherited, if the child class also defines its own class Meta.
I found the following work-around, which saves from having to define the permissions again on every child model:
class AbstractBaseModel(models.Model):
class Meta:
abstract = True
permissions = (("test_permission","test permission"),)
class SomeClass(AbstractBaseModel):
name = models.CharField(max_length=255,verbose_name="Name")
class Meta(AbstractBaseModel.Meta):
verbose_name = ....
No need to set abstract to false in the child Meta class, since Django sets it in the parent to False when processing it! http://docs.djangoproject.com/en/dev/topics/db/models/#meta-inheritance
Here is link for resolve you issue: http://code.djangoproject.com/ticket/10686
Need to apply patch... But it realy works.
My work-around:
class AbstractModelBase(models.base.ModelBase):
def __new__(cls, name, bases, attrs):
new = super(AbstractModelBase, cls).__new__(cls, name, bases, attrs)
new._meta.permissions += (("abstract_permission", "Abstract permission"),)
return new
class AbstractModel(models.Model):
__metaclass__ = AbstractModelBase
class Meta:
abstract = True
take a look at the following meta implementation,
it adds read permissions to all django models that set MyModelMeta Class to be thier metaclass:
class MyModelMeta(ModelBase):
# add a read permission to each MyModelMeta model
def __new__(cls, name, bases, attrs):
Meta = None
if "Meta" in attrs:
Meta = attrs.get("Meta")
if hasattr(Meta, "abstract") and getattr(Meta, "abstract"):
# if the class is abstract, don't create permissions for it, just return the class object
return super(MyModelMeta, cls).__new__(cls, name, bases, attrs)
if not Meta:
# create a new Meta Class
Meta = type('Meta', (object,), {})
setattr(Meta, 'permissions',(("read_%s"%name.lower(), "Can read %s"%name.lower()),))
attrs['Meta'] = Meta
return super(MyModelMeta, cls).__new__(cls, name, bases, attrs)
create an abstract django models and set the meta class memeber to MyModelMeta:
class MyAbstractModel(models.Model):
__metaclass__ = MyModelMeta
class Meta:
abstract=True
now, create a normal django model like so:
class SomeModel(MyAbstractModel):
someFieldName = models.CharField(max_length=256, db_index=True)
this will generate the default add/change/delete_somemodel permissions, but also
it will add a new read_somemodel permission.
if you're also using south, use this to generate the extra permissions:
from django.db.models import get_app, get_models
from django.contrib.auth.management import create_permissions
create_permissions(get_app(app), get_models(), 2 if settings.DEBUG else 0)
I write test for your issue.
I use django 1.2.1 and i have great result!
If you want to add permission to your existing model from inherit model, every time when you change them you need to run "syncdb".
Example 100% works.(in 1.2.1 without patch)
It now works.
alt text http://img203.imageshack.us/img203/7500/permn.png
Example:
from django.db import models
from django.contrib import admin
class permissions(models.Model):
class Meta:
abstract = True
permissions = (("test_permission","test permission"),)
class SomeClass(permissions):
name = models.CharField(max_length=255,verbose_name="Name")
admin.site.register(SomeClass)
In my case explicitly inheriting the Meta didn't work because of South. See this ticket.
django-admin.py syncdb --all fixed the problem.