What is/are the best practices to use get_model() and when should it be imported ?
Ref: https://docs.djangoproject.com/en/1.8/ref/applications/
You usually use get_model() when you need to dynamically get a model class.
A practical example: when writing a RunPython operation for a migration, you get the app registry as one of the args, and you use apps.get_model('TheModel') to import historical models.
Another example: you have an app which has dynamically built serializers and you set their Meta.model to the class you just got with get_model() .
Yet another example is importing models in AppConfig.ready() with self.get_model().
An important thing to remember, if you are using AppConfig.get_model() or apps.get_models(), that they can be used only once the application registry is fully populated.
The other option (from .models import TheModel) is just the default way to import models anywhere in your code.
These are just examples though, there are many other possible scenarios.
I Prefer, use .models import, cause is a simple way to get the Model Object.
But if you works with metaclasses, maybe the get_model, would be the best option.
def get_model(self, app_label, model_name=None):
"""
Returns the model matching the given app_label and model_name.
As a shortcut, this function also accepts a single argument in the
form <app_label>.<model_name>.
model_name is case-insensitive.
Raises LookupError if no application exists with this label, or no
model exists with this name in the application. Raises ValueError if
called with a single argument that doesn't contain exactly one dot.
"""
self.check_models_ready()
if model_name is None:
app_label, model_name = app_label.split('.')
return self.get_app_config(app_label).get_model(model_name.lower())
Maybe this SO POST, can help too.
Related
This is an extension from my post here preventing crud operations on django model
A short into to the problem , im currently using a package called django-river to implement a workflow system in my application. The issue is that they do not have a predefined 'start' , 'dropped' , 'completed' state. Their states are stored as a django model instance. This would mean that my application is unable to programmatically differentiate between the states. Therefore , the labels of these states has to be hardcoded into my program (Or does it? Maybe someone has a solution to this?)
Suppose that there is no solution to the issue other than hardcoding the states into my application , this would mean that i would have to prevent users from updating , or deleting these states that i have pre created initially.
My idea is to have a form of validation check within the django model's save method . This check would check that the first 3 instances of the State model is always start , deactivated and completed and in the same order. This would prevent the check from passing through whenever a user trys to change items at the ORM level.
However , it would seem that there is 2 issues with this:
I believe django admin doesn't run the model class save method
Someone is still able to change the states as long as the way they changed it does not pass through the save() method. AKA from the DB SQL commands
Although it is unlikely to happen , changing the name would 'break' my application and therefore i wish to be very sure that no one can edit and change these 3 predefined states.
Is there a fool proof way to do this?
My idea is to have a form of validation check within the django model's save method.
if i understand your description, maybe you can just override the save() function of your model like so:
class MyModel(models.Model):
[..]
def save(self, *args, **kwargs):
# Put your logic here ..
super(MyModel, self).save(*args, **kwargs)
I got the answer from django documentation
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
You can add this to a model field via the field’s validators argument:
from django.db import models
class MyModel(models.Model):
even_field = models.IntegerField(validators=[validate_even])
FYI: It is not really mandatory to use gettext_lazy and you can use just message as follows
from django.core.exceptions import ValidationError
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
('%(value)s is not an even number'),
params={'value': value},
)
I'd like to run a custom command in my migration, that calls functions from other modules. These functions use some models, and as expected I ran into schema version mismatch (OperationalError: (1054, "Unknown column 'foo' in 'bar'").
If I were to use those models in the custom command I'd access the model with apps.get_model('my_app', 'bar'), but as those models are used in the external functions, I can't do that.
I'm sure, someone ran into this before although I couldn't find anything.
I was thinking about using the unittest.mock.patch decorator but it doesn't feel like the right solution.
I'm wondering if there's a more general solution for this?
The versioned app registries are not globally accessible. You could pass the model as a parameter to the function, and use the current model as the default:
from my_app.models import Bar
def my_function(..., bar_model=Bar):
# Use bar_model instead of Bar
# Your RunPython function
def migrate_something(apps, schema_editor):
my_function(bar_model=apps.get_model('my_app', 'bar'))
You don't have to pass the bar_model parameter if you call it from regular code, but when calling it from a migration you can pass the historical model.
If you need multiple models you could pass apps instead:
from django.apps import apps as global_apps
def my_function(..., apps=global_apps):
Bar = apps.get_model('my_app', 'bar')
there are plenty of examples the tell you how to extend the user model BUT I cannot find a real, complete and documented example on how to extend an existing model without having to follow the "user profile pattern" (and honestly I wonder why).
In short, my use case is the following: I need to extend django-lfs's product model.
In LFS is registered like this (in lfs.catalog.admin):
from django.contrib import admin
[...]
from lfs.catalog.models import Product
[...]
class ProductAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name", )}
admin.site.register(Product, ProductAdmin)
[...]
I tried to register mine (that subclasses it) but I got:
django/contrib/admin/sites.py",
line 78, in register
raise AlreadyRegistered('The model %s is already registered' %
model.name)
So, someone suggested me that I have to unregister that object and register mine.
I did it like this:
from lfs.catalog.models import Product
from lfs.catalog.admin import ProductAdmin
admin.site.unregister(Product)
from lfs_product_highlights.catalog.models import Product
admin.site.register(Product,ProductAdmin)
No errors this time BUT there's no change, my custom fields are nowhere to be seen.
Any hints?
The reason why it's difficult is because of the object-relational impedance mismatch (love that phrase). Objects and classes do not map perfectly onto relational databases: ORMs like Django's attempt to smooth out the edges, but there are some places where the differences are just too great. Inheritance is one of these: there is simply no way to make one table "inherit" from another, so it has to be simulated via Foreign Keys or the like.
Anyway, for your actual problem, I can't really see what's going on but one possible way to fix it would be to subclass ProductAdmin as well, and specifically set the model attribute to your subclassed model.
Example:
class MyModel(models.Model):
field1=models.CharField(..)
field2=models.DateTimeField()
def today(self):
return self.field2
When I look at this in the admin site, field2 is formatted differently than the today field.
How can I tell the admin site to treat today like it's treating field2? I.e., tell Django admin that 'today' is a models.DateTimeField?
Here is what it's showing:
Field2 today
April 5, 2011, 9:10 a.m. 2011-04-11 08:47:27
To obtain DateTime object call datetime.datetime.now() instead of datetime.datetime.today()
EDIT:
Or use models.DateField() for field2 instead of models.DateTimeField() :-)
EDIT2:
Here is the solution:
def today(self):
from django.utils import formats
return formats.localize(self.field2)
That's some really really weird behaviour. At a total guess, it may have something to do with django settings; specifically the DATETIME_FORMAT (and related) settings. The framework probably does introspection on fields, and if they are of DateTime type, are rendered according to the aforementioned settings.
Introspection on methods wouldn't make sense in the majority of cases, so I could understand this behaviour if it is the case.
Try modifying the settings accordingly (provide different datetime formats), and see if the fields change and the method remains the same.
Edit:
Looking at django.contrib.databrowse.datastructures, there is a section of code that does something like:
if isinstance(self.field, models.DateTimeField):
objs = capfirst(formats.date_format(self.raw_value, 'DATETIME_FORMAT'))
I'd imagine a similar thing happening within the admin app, though I can't find an exact reference at the moment.
To achieve what you want, you'll need to format your datetime appropriately:
def today(self):
from django.conf import settings
return self.field2.strftime(settings.DATETIME_FORMAT)
Or, using #cata's comment:
def today(self):
from django.utils.formats import localize
return localize(self.field2)
If you choose to supply a "list_display" item through your own function, and you're not happy with the default output, you'll need to format it yourself. In this case, if you want to have identical formatting to what the DateTime database field ends up with:
from django.utils import formats
def today(self):
return formats.localize(self.field2)
Background:
templates/admin/change_list.html
uses the template tag
django.contrib.admin.templatetags.admin_list.result_list
which in turn will call
django.contrib.admin.templatetags.admin_list.items_for_result()
to render the individual column values for each row.
You'll see that both your values start off being a DateTime instance, either through database lookup or calling your function, see
django.contrib.admin.util.lookup_field()
but the return value "f" will only be a field if there was a database field. You provided a function, so lookup_field() will only provide the value, and "f" will be None.
So in items_for_result(), your value will run through the "if f is None" block and miss out on
result_repr = display_for_field(value, f)
In other words,
django.contrib.admin.util.display_for_field()
will only be called on the database value to format according to the field type, so this is the treatment your function value is missing out on:
elif isinstance(field, models.DateField) or isinstance(field, models.TimeField):
return formats.localize(value)
and you'll need to do that last line yourself, as shown above.
EDIT: Regarding your question
How can I tell the admin site to treat
today like it's treating field2? I.e.,
tell Django admin that 'today' is a
models.DateTimeField?
It's not a models.DateTimeField, it's a function value. If it were a models.DateTimeField, it would be describing your model. Look at all the stuff that entails: http://docs.djangoproject.com/en/dev/ref/models/fields/
In your example, you really could just use field2. Apparently you want to do things to its value, calculate it etc. - so what's today.db_column then?
That said, it would be nice if function values that are DateTime instances were run through format.localize() by default, as that's what the documentation on localization seems to be promising.
By the way, I would rather define a formatted value in the ModelAdmin than in the model itself. I usually call it something like "formatted_today" (to keep the datetime value of the original today()), it's just that if the Admin is the only place that needs the formatted value, imho that's where it should be defined.
All previous answers provide solutions, that will handle timezone info incorrectly in new Django versions.
field2 and today will generally show different time if settings.USE_TZ==True.
I found this question today and have spent some time to figure out the correct way:
from django.db import models
from django.contrib import admin
from django.utils.timezone import template_localtime
from django.utils.formats import localize
class MyModel(models.Model):
# ...
field2=models.DateTimeField()
class MyModelAdmin(admin.ModelAdmin):
# ...
def today(self, obj):
return localize(template_localtime(obj.field2))
admin.site.register(MyModel, MyModelAdmin)
I have a multiple ModelForm classes that each represent a different Model. I would like to have a generic 'create' function that loads the specified model form based on a URL parameter. It is possible to load a model dynamically with this:
model_name = 'TestModel'
m = get_model('AppLabel', model_name)
Does anyone know how I can achieve the same for ModelForms, something like:
modelform_name = 'TestModelForm'
f = get_form('AppLabel', modelform_name)
if f.is_valid():
...
I can not think of a way to do this with generic views - they require the ModelForm to be passed, rather than just its name. If I get the model with get_model then pass that to the generic view it will display a form but I am unable to exclude model fields.
TIA for any tips
When you create a ModelForm it does not register itself with its model's app. (Based on experience and a quick browse through the source).
Here are some otheroptions I can think of:
All ModelForm classes exist in a single module: Use getattr on that module based on the string.
ModelForm's are spread out among many models and you have a reasonable (<30) amount of forms:
Create a dictionary mapping from form strings you expect to ModelForm classes. For example:
from some_app.forms import FirstModelForm
from another_app.forms import SecondModelForm
from additional_app.forms import FirstModelForm as AdditionalAppFirstModelForm # Will allow for managing conflicting names easily.
form_mapping = {
'FirstModelForm': FirstModelForm,
'SecondModelForm': SecondForm,
'AdditionalAppFirstModelForm': AdditionalAppFirstModelForm,
}
request_form_class = request.POST.get('form_class')
f = form_mapping.get(request_form_class)(request.POST)
if f.is_valid():
f.save()
You're dealing with a lot of forms: Create a baseclass for your ModelForm, or replace the BaseModelFormMetaclass at runtime. You'll have to deal with issues such as name conflicts, duplicate ModelForms for the same Model "automagically", so prepare for some headaches. It would be pretty rad if you could pull it off.
Personally (as you can probably see), I'd just go with option #2.
An alternate method for this is to replace forms.py with a package called forms. Then, in __init__.py within that package, import all your ModelForms.
Then you can use sdolan's Option #1.