Django conditionally defined model field in abstract model - django

I've got an abstract model in my project that I want to use to define a field by default on concrete subclasses, but also to allow that field to be redefined as something other than the default dynamically. All of this works right now:
class classproperty(object):
"""
Decorator for making class properties
"""
def __init__(self, fget):
self.fget = fget
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class BaseModel(models.Model):
class Meta(object):
abstract = True
#classproperty
def _special_attribute_field(self):
return getattr(self, '_bm_special_attribute_field', 'default')
#property
def bm_special_attribute(self):
return getattr(self, self._special_attribute_field)
...and then there are a bunch of methods that use the latter two functions to figure out which field to access.
The problem is that right now, classes inheriting from BaseModel have to define the default field explicitly, even if they don't use _bm_special_attribute_field to specify something other than the default. What I'd like to do is programmatically define default on concrete submodels only if those models don't use _bm_special_attribute_field to change it to something else, in which case, they should bring their own field. Is there a way to do this, perhaps with metaclasses? The key thing being that it has to not muck up the Django machinery.

Related

How to make a generic List Filter in Django using django-filters?

I want to have a FilterSet class that allows all of the fields to be filtered as a list.
I was thinking of overriding the default FilterSet class of django-filters and having the fields be dynamically set as well as setting a method to each one.
So, something like this for example:
ModelName_fields = {'names': 'name', 'ids': 'id', 'second_ids': 'second_id'}
class ListFilter(FilterSet):
def __init__(self, *args, **kwargs):
# self._meta.fields = [field_name for field_name in modelNameHere + '_fields']
super(FilterSet).__init__(*args)
class SomeAPIView(mixins.ListModelMixin):
model = ModelName
filterset_class = ListFilter
Basically, ModelName_fields is a declared constant that maps the query parameter to the field name of the model. In this case, I declare the model on the view as well as the filterset class and in the __init__ method of the filterset class, I dynamically attach the fields as well as the query parameter name.
In all essence, I just want to make the ListFilter as generic as possible to be used on different views as well.
My question is, is this the correct way or is there some other better way to accomplish this? Also, how can I get the name of the model, which is an attribute of the view class, in the ListFilter class?

Setting attributes in constructor for Django model

I have an abstract BaseModel from which many models will inherit the behavior to initialize attributes values from the constructor:
class BaseModel(models.Model):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key in kwargs:
setattr(self, key, kwargs[key])
class Meta:
abstract = True
Then, I try to initialize one of its specializations like this:
BaseModelSon(attr1=val1, attr2=val2, ...)
Is this a good practice? Do you know any other way to achieve this dynamic initialization for Django models?
You can already create regular Django model instances with
MyModel(field1='value1', field2='value2')
so I don't think your __init__ method is necessary.
Also you'll probably find useful Model.objects.create() method for new Django models instances creation:
instance = MyModel.objects.create(field1='value1', field2='value2'...)
If there are some auto-generated fields (for example, id or some date with auto_now_add=True option) — you'll get their values in instance if you have used Model.objects.create().

Django admin short_description as callable

Is there a way to define short_description for a field in ModelAdmin as callable - sometimes one wants to provide extra dynamic information inside column names.
Or are there any ugly hacks that accomplish the same goals?
As far as I know/remember properties can only be defined on new-style classes, in the class definition. So your ModelAdmin field would have to resolve to a callable object with the desired property. This snippet looks to me like it should allow that:
https://djangosnippets.org/snippets/2447/
The field itself becomes a property that resolves to an instance of the VotesToday class, on which short_description is also a property.
Peter DeGlopper's answer provided the needed direction - despite the fact that since the djangosnippet's posting a lot of things have changed.
This is indeed working:
class MyAdmin(admin.ModelAdmin):
list_display = ('my_callable')
class MyCallable:
def __call__(self, obj):
return 42
#property
def __name__(self):
return 'Galaxy'
#property
def my_callable(self):
if not hasattr(self, __my_callable):
self.__my_callable = self.MyCallable()
return self__my_callable
Importantly enough, the MyAdmin object is not passed to the MyCallable.__call__() call - if you need access to it, pass it in the __init__ initializer yourself.

Subclassing AbstractUser in Django for two types of users

I'm developing a school database system in Django 1.5, and was planning on having a number of different user types (Student, Staff, Parent) which subclass AbstractUser (actually, another abstract subclass of AbstractUser). I was just attempting to add an externally developed app to my system, which uses User in a ForeignKey for some of its models, however, this fails as my user type is not a 'User' instance. I can't set the apps models to use AbstractUser as one can't use abstract classes for Foreign Keys. I was then considering adding to my settings.py AUTH_USER_MODEL = 'myapp.MyUser' and using settings.AUTH_USER_MODEL in place of User for the ForeignKey in the app. However, I have 3 different user types, so can't do this either.
An earlier prototype used Django 1.4, which did not support custom User models, hence had a reference to a User instead, but this required an extra join for every query, which was leading to quite complex queries. Is this the only way I can go forward with this, or is there another solution?
I have successfully used the following solution:
1. Create SchoolUser class in models.py - this will be your AUTH_USER_MODEL class
TYPES = (('Student', 'Student'), ('Staff', 'Staff'), ('Parent', 'Parent'), )
class SchoolUser(AbstractUser):
type = models.CharField(max_length=10, choices=TYPES, default='Student')
2. Create users.py file and put whole users logic there. Have one abstract class that all others inherit from and which will implement the factory method:
class UserManager(object):
def __init__(self, user):
self.user = user
#classmethod
def factory(cls, user):
"""
Dynamically creates user object
"""
if cls.__name__.startswith(user.type): # Children class naming convention is important
return cls(user)
for sub_cls in cls.__subclasses__():
result = sub_cls.factory(user)
if result is not None:
return result
Sample children classes (also go to users.py file):
class StudentUser(UserManager):
def do_something(self):
pass
class StaffUser(UserManager):
def do_something(self):
pass
class ParentUser(UserManager):
def do_something(self):
pass
Views is where the magic happens ;)
def my_view(request):
school_user = UserManager.factory(request.user)
if school_user.do_something: # each class can have different behaviour
This way you don't need to know, which type of user it is, just implement your logic.
I hope this is clear enough, if not let me know!

django not taking in consideration model fields declared in __init__

When using Model class like this:
class MyModel(models.Model):
def __init__(self, *args, **kwargs):
self.myfield = models.Field()
super(MyModel, self).__init__(*args, **kwargs)
It doesn't take into consideration myfield(in the admin form, when saving the object... )
But if i declare like that:
class MyModel(models.Model):
myfield = models.Field()
It works just fine.
Why?
Edit
I think i have a good reason: I have an abstract class UploadItem that defines a field called file like this: self.file = models.FileField(upload_to=upload_to) As you can see, in each child class, i have to call parent init method with appropriate upload_to variable(say 'videos' for Video model). So i cannot do it the normal way.
Because the Django ORM code does some serious meta-magic during class definition (just browse the django/db code to see how magic). You are doing an end-run around that magic by creating fields on the fly in the __init__() function.
Is there a really good reason for not creating the class in the normal way? If not, then do it the normal way. If you do have a good reason then get ready to get into the really deep end of the pool -- both of Python and Django.
Setting a dynamic path for the upload_to attribute is absolutely not a good reason for wanting to muck around with model field declaration.
This is something that Django handles already - if you set upload_to to a callable, you can return the correct value dependent on the model instance. See the documentation.