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.
Related
For a Django model I'm using django-import-export package.
The manual says I can export fields that are not existing in the target model like so:
from import_export import fields
class BookResource(resources.ModelResource):
myfield = fields.Field(column_name='myfield')
class Meta:
model = Book
http://django-import-export.readthedocs.org/en/latest/getting_started.html
How do I export the output of functions from the model? e.g. Book.firstword()
Here's how you should do it (check this out https://django-import-export.readthedocs.org/en/latest/getting_started.html#advanced-data-manipulation):
from import_export import fields, resources
class BookResource(resources.ModelResource):
firstword = fields.Field()
def dehydrate_firstword(self, book):
return book.firstword()
class Meta:
model = Book
Update to answer OP comment
To return fields in a particular order, you can user the export_order Meta option (https://django-import-export.readthedocs.org/en/latest/api_resources.html?highlight=export_order#import_export.resources.ResourceOptions).
There is one more solution with less code, than that suggested by Serafeim:
from import_export import fields, resources
class BookResource(resources.ModelResource):
firstword = fields.Field(attribute='firstword')
class Meta:
model = Book
Just in case you need to get the full URL of the field and based on #Serafeim's solution
class CompanyModelResource(ModelResource):
def dehydrate_local_logo(self, company):
if company.local_logo and hasattr(company.local_logo, 'url'):
return company.local_logo.url
return company.local_logo
class Meta:
model = Company
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)
I am adding a slug to all my models for serialization purposes, so I have defined an abstract base class which uses the AutoSlugField from django_autoslug.
class SluggerModel(models.Model):
slug = AutoSlugField(unique=True, db_index=False)
class Meta:
abstract=True
I also have a custom manager and a natural_key method defined, and at this point I have about 20 child classes, so there are several things that make using an abstract base model worthwhile besides just the single line that defines the field.
However, I want to be able to switch a few of the default arguments for initializing the AutoSlugField for some of the child models, while still being able to utilize the abstract base class. For example, I'd like some of them to utilize the populate_from option, specifiying fields from their specific model, and others to have db_index=True instead of my default (False).
I started trying to do this with a custom Metaclass, utilizing custom options defined in each child Model's inner Meta class, but thats become a rat's nest. I'm open to guidance on that approach, or any other suggestions.
One solution would be to dynamically construct your abstract base class. For example:
def get_slugger_model(**slug_kwargs):
defaults = {
'unique': True,
'db_index': False
}
defaults.update(slug_kwargs)
class MySluggerModel(models.Model):
slug = AutoSlugField(**defaults)
class Meta:
abstract = True
return MySluggerModel
class MyModel(get_slugger_model()):
pass
class MyModel2(get_slugger_model(populate_from='name')):
name = models.CharField(max_length=20)
Update: I started out with the following solution, which was ugly, and switched to Daniel's solution, which is not. I'm leaving mine here for reference.
Here's my Metaclass rat trap that seems to be working (without extensive testing yet).
class SluggerMetaclass(ModelBase):
"""
Metaclass hack that provides for being able to define 'slug_from' and
'slug_db_index' in the Meta inner class of children of SluggerModel in order to set
those properties on the AutoSlugField
"""
def __new__(cls, name, bases, attrs):
# We don't want to add this to the SluggerModel class itself, only its children
if name != 'SluggerModel' and SluggerModel in bases:
_Meta = attrs.get('Meta', None)
if _Meta and hasattr(_Meta, 'slug_from') or hasattr(_Meta, 'slug_db_index'):
attrs['slug'] = AutoSlugField(
populate_from=getattr(_Meta, 'slug_from', None),
db_index=getattr(_Meta, 'slug_db_index', False),
unique=True
)
try:
# ModelBase will reject unknown stuff in Meta, so clear it out before calling super
delattr(_Meta, 'slug_from')
except AttributeError:
pass
try:
delattr(_Meta, 'slug_db_index')
except AttributeError:
pass
else:
attrs['slug'] = AutoSlugField(unique=True, db_index = False) # default
return super(SlugSerializableMetaclass, cls).__new__(cls, name, bases, attrs)
The SlugModel looks basically like this now:
class SluggerModel(models.Model):
__metaclass__ = SluggerMetaclass
objects = SluggerManager()
# I don't define the AutoSlugField here because the metaclass will add it to the child class.
class Meta:
abstract = True
And I can acheive the desired effect with:
class SomeModel(SluggerModel, BaseModel):
name = CharField(...)
class Meta:
slug_from = 'name'
slug_db_index = True
I have to put SluggerModel first in the inheritance list for models having more than one abstract parent model, or else the fields aren't picked up by the other parent models and validation fails; however, I couldn't decipher why.
I guess I could put this an answer to my own question, since it works, but I'm hoping for a better way since its a bit on the ugly side. Then again, hax is hax so what can you do, so maybe this is the answer.
Let's suppose I have the following:
class Base(Model):
m2m_1 = ManyToManyField("SomeModel1")
m2m_2 = ManyToManyField("SomeModel2")
class Meta:
abstract = True
class A(Base):
def __init__(self):
super(A, self).__init__()
pass
class B(Base):
def __init__(self):
super(B, self).__init__()
pass
However, I cannot do that because it requires related name for M2M field. However, that does not help as the model is abstract and django tries to create the same related name for both A and B models.
Any ideas how to specify related names for each model separately or even do not use them at all?
The answer is right in the docs for abstract classes (under section entitled "Be careful with related_name"):
m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")
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')