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.
Related
I am trying to use a CreateView to create a profile object. Depending on the type of profile, though, I want to show a different set of fields. Right now I am passing the type of profile through the url.
How would I go about accessing this argument to filter the types of fields? I know how to do so in something like a get() function in a CBV, but not in the body of the class itself. I also do not know if there is an appropriate function to do this in and have not been able to find a solution in the docs.
Thank you in advance for any help, and I am sorry if this question has an obvious answer that I have missed.
You would need to override some method. Potential methods that can work are get_form_class, get_form, etc. Try overriding get_form:
class MyView(CreateView):
model = SomeModel
fields = None
def get_form(self):
self.fields = ['field1', 'field2'] # set this according to your conditions
# the keyword arguments passed to the view can be accessed by using self.kwargs.get('<key_name>')
return super().get_form()
What I Need
I want to have a global configuration for my app and I want to reuse a generic UpdateView.
What I Tried
For this purpose I created a model (example fields):
class Configuration(models.Model):
admin = models.ForeignKey('User', on_delete=models.CASCADE)
hostname = models.CharField(max_length=23)
A generic Updateview:
class ConfigurationView(UpdateView):
model = Configuration
fields = ['admin','hostname']
And urls.py entry
path(
'configuration/',
views.ConfigurationView.as_view(
queryset=Configuration.objects.all().first()
),
name='configuration'
),
As you can see I want the configuration/ path to link to this configuration and always only edit this one object.
Problem
I get the error
AttributeError: 'Configuration' object has no attribute 'all'
Questions
How can I hardcode the object into the path in urls.py so that always the first Configuration object is used for the UpdateView?
Is there a better way to do this? I simply want to have a global configuration object and want it to be editable and displayable with a template of my choice.
You're trying to provide a single object to a class expecting a queryset. The view calls get_queryset which does this;
def get_queryset(self):
"""
Return the `QuerySet` that will be used to look up the object.
This method is called by the default implementation of get_object() and
may not be called if get_object() is overridden.
"""
if self.queryset is None:
if self.model:
return self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
return self.queryset.all()
You've provided a queryset so that lands on self.queryset.all() which for your example is calling all() on an instance of your class.
To use the queryset kwarg of as_view() you'd do something like MyView.as_view(queryset=MyModel.objects.filter(enabled=True))
So you need to change the way the view looks for the object;
class ConfigurationView(UpdateView):
def get_object(self):
return Configuration.objects.first()
By default UpdateView does this to get an object; https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/UpdateView/
If you're limiting the config to 1 object you will also want to implement a Singleton design. Essentially this is a way to ensure only 1 object can exist. Read more here; https://steelkiwi.com/blog/practical-application-singleton-design-pattern/
There is a really helpful package for singletons called django-solo
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.
Consider the following code:
class MyManyToManyField(models.ManyToManyField):
def __init__(self,*args,**kwargs):
field_name = ?!?
kwargs["related_name"] = "%(app_label)s.%(class)s." + field_name
super(MetadataManyToManyField,self).__init__(*args,**kwargs)
class MyModelA(models.Model):
modelAField = MyManyToManyField("MyModelB")
class MyModelB(models.Model):
pass
Is there any way for me to access the name of the field from within my overloaded init function? I want the related_name of modelAField to wind up being "MyAppName.MyModelA.modelAField".
I've thought about just passing it as a kwarg:
modelAField = MyManyToManyField("MyModelB",related_name="modelAField")
and then using it in init:
field_name = kwargs.pop("related_name",None)
if not field_name:
raise AttributeError("you have to supply a related name!")
But I'm hoping for something a bit nicer.
Thanks.
Use contribute_to_class
As has been mentioned before, a field object has to be instantiated, by calling its __init__ function, before it can be assigned to a variable within the class.
However, while the model is being constructed, Django looks for a method on the field called contribute_to_class. If it is present, it will be called like this:
new_field.object.contribute_to_class(ModelClass, field_name)
If you override this method in your custom field class, you can perform whatever initialisation is required at the point where the field is added to the model.
I don't think this is possible. The object MyManyToManyField is going to be instantiated before it's assigned to a variable, so it's too early to be able to do anything clever.
There might be same magic in Django's metaclass for models which interacts with the fields, it might be possible to hijack this interaction and put you logic there. But I'm just speculating at this point.
Passing as a kwarg doesn't seem that cumbersome.
I suspect that Python can't decide what the name of an instance is, since an object can have many reference pointers.
Eg
x = YourClass()
y = x
Is the name of the instance of YourClass 'x' or 'y'? Or is it just an instance of YourClass?
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.