Django get the Model name using ORM's related field name - django

I have a string like this order__product__category__description which is a related expression of my Django's model structure
Now I have a model called Shipment
field = 'order__product__category__description'
Here description is a column name of table/model Category. Here comes the question, just by having this model Shipment and this field string order__product__category__description how do I find these models Order, Product, Category.
My use-case is I need to store all the field_names of Category in a list. Any idea on how to connect the dots? having left with just two details Shipment & that field string.
First thing comes to mind is to split by __ and to come up with a list like this ['order','product','category'] and to iterate the model _meta based on the field name. Any other elegant ways would be appreciated.

If you want to get the related model from the model class (rather than the related instance from a model instance), you can use _meta.get_field() combined with field.related_model to get the related model class:
from django.core.exceptions import FieldDoesNotExist
model = Shipment
lookup = 'order__product__category__description'
for name in lookup.split('__'):
try:
field = model._meta.get_field(name)
except FieldDoesNotExist:
# name is probably a lookup or transform such as __contains
break
if hasattr(field, 'related_model'):
# field is a relation
model = field.related_model
else:
# field is not a relation, any name that follows is
# probably a lookup or transform
break
Then do with model what you want.

I didn't understand well. Anyway hope this you want.
field = 'order__product__category__description'
To get product from Shipment instance
product_var = ".".join(field.split("__")[:2])
Then
from operator import attrgetter
attrgetter(product_var)(shipment_instance)
Also you can get all related as a tuple
attrgetter(".".join(field.split("__")[:1]), ".".join(field.split("__")[:2]), ".".join(field.split("__")[:3]), ".".join(field.split("__")[:4]))(shipment_instance)
Hope this helps.

Assuming you have split the string it into a list, as you said to these:
models = ['order', 'product', 'category']
First get the app label (a string) that each model refers to:
from django.contrib.contenttypes.models import ContentType
app_labels = []
for model in models:
app_labels.append(ContentType.objects.filter(model=model).values('app_label').first().get('app_label'))
Now you have both model names and app names. So:
from django.apps import apps
model_classes = []
for app, model in zip(app_labels, models):
model_classes.append(apps.get_model(app, model))
Finally, get each model fields with the _meta attribute like you already know:
for model in model_classes:
print(model._meta.get_fields())

Related

varieties on '*lookups' arguments for the function 'prefetch_related'

I can't figure out the difference between prefetch_related('arg_set') and prefetch_related('arg') .
Sometimes prefetch_related doesn't work when using argument 'arg'even 'arg_set' works.
I've searched through docs.djangoproject.com but, at least, I can't find related document on both pages below.
https://docs.djangoproject.com/en/2.1/ref/models/querysets/ https://docs.djangoproject.com/ja/2.1/ref/contrib/contenttypes/
Could some of you elaborate the difference and when _set is necessary?
And I want to read the official document related to this issue so showing me the reference link is appreciated.
Thank you in advance.
environment:
windows10, python 3.7.2, django 2.1.8, sqlite3, PyCham 2019.1 .
views.py
from django.shortcuts import render
from .models import Article
def index(request):
a = Article.objects.all().select_related('user').prefetch_related('comment_set').order_by('id') # [1]
a = Article.objects.all().select_related('user').prefetch_related('comment').order_by('id') # [2]
return render(request,
'sns/index.html',
{'articles': a})
models.py
from django.db import models
from article_comment_model.settings import AUTH_USER_MODEL
class Article(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='article_user')
title = models.CharField(max_length=100)
text = models.TextField()
class Comment(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='comment_user')
article = models.ForeignKey(Article, on_delete=models.CASCADE)
text = models.TextField()
I want to understand the variety of prefetch_related arguments well.
The argument for callig prefetch_related() would be the name of the relation. In your case this would be a reverse ForeignKey relationship. As you can see in the documentation the name of the reverse relationship will be FOO_set where FOO is the model's lowercase name.
Therefore in your example prefetch_related('comment_set') should be correct. If you would specify a related_name like
article = models.ForeignKey(Article, on_delete=models.CASCADE,
related_name='comments')
the related_name will get used instead of FOO_set, therefore prefetch_related('comments') should be valid in this case.
The name of the lookup to use in prefetch_related depends on the answer to a couple of questions:
Is the relation defined on the model you are querying or on the other side of the relationship?
If the relation is defined on the model you are querying (termed "forward relationship" by the Django docs), then the lookup is simply the field name. If the relation is defined at the other end of the relationship ("backward relationship"), then the lookup depends on a second question:
Have you defined a related_name in the other model where the relationship is defined?
If yes, the lookup is the related_name. If there is no related_name, Django uses a default name, which is modelname_set for x-to-many relationships and modelname for x-to-one relationships (both in lower case).
In practical terms, this means the following in your code:
x-to-many relationships:
# no related names defined, using default manager name
Article.objects.prefetch_related('comment_set')
# using related names
User.objects.prefetch_related('article_user', 'comment_user')
x-to-one relationships:
Article.objects.prefetch_related('user')
Comment.objects.prefetch_related('article', 'user')
Using prefetch_related for x-to-one relationships like in the last two examples above is rare. We mostly use select_related as the latter only creates a join in the original query instead of issuing a separate query. However, as you can read towards the end of its documentation, prefetch_related has a few potential advantages. It can:
fetch a filtered queryset
fetch incomplete models (via only and defer)
perform nested prefetching via the Prefetch object

access model fields and attributes in django

I'm writing a simple scaffolding app in django, however I'm still not able to access a models fields and attributes(e.g. CharField, max_length=100, null=True, etc...). I know about the _meta class of models, but as far as I know it only retrieves basic info about the model not the fields. Is there a away to achieve this?
Update:
you can find the answer in this article:
http://www.b-list.org/weblog/2007/nov/04/working-models/
You should use the get_field method to get info for a particular field:
field = ModelName._meta.get_field('field_name')
Then you may check various attributes of field, like field.blank, field.null, field.name etc.
If on the other hand you want to get a list of all fields of a Model you should use fields:
fields = ModelName._meta.fields
For example to get the name of all your model fields you can do something like:
field_names = ', '.join(f.name for f in fields)
Hm... also I just noticed that your question is a duplicate of Get model's fields in Django !

Django m2m field. Through model with multiple foreign keys (fk) to same model type

I wanted all instances in all tables to have an object instance. A one to one primary key field looked like a good way to do this. Like a small example below.
from util.fields import BigAutoField,BigForeignKey
from django.db import models
class Common_document(models.Model):
pk = models.OneToOneField("Type_object", primary_key=True, related_name="common_document_content_pk")
author = models.ManyToManyField("Type_object", related_name = 'common_document_author',
limit_choices_to = {"Common_document_author_author":"Type_object"} ,null = True,
through = 'Common_document_author', blank = True)
#....
class Common_document_author(models.Model):
pk = models.OneToOneField("Type_object", primary_key=True, related_name="common_document_author_pk")
document = BigForeignKey("Common_document", related_name='Common_document_author_document')
author = BigForeignKey("Type_object", related_name='Common_document_author_author')
class Type_object(models.Model):
id = BigAutoField(primary_key=True)
#....
# Some of the fields are m2m
However this gave the following error:
django.core.management.base.CommandError: One or more models did not validate:
schema.common_document: Intermediary model Common_document_author has more than
one foreign key to Type_object, which is ambiguous and is not permitted.
This error is removed if I comment out the pk field in the document_author table. I guess the error comes because django is not sure witch object FK to use. How do i fix this? Is there a way to tell django which field in the m2m table to be used in the m2m field?
I am probably not going to do it like this. m2m tables are probably not going to need to have an object instance, but I would still like to know how to do this.
I guess I don't understand you motivation. Why do you want to use a foreign key as your primary index? Sure, index it, but primary?. You might also try changing its name from 'pk', I am sure Django makes assumptions about the field called 'pk'.

Creation of dynamic model fields in django

This is a problem concerning django.
I have a model say "Automobiles". This will have some basic fields like "Color","Vehicle Owner Name", "Vehicle Cost".
I want to provide a form where the user can add extra fields depending on the automobile that he is adding. For example, if the user is adding a "Car", he will extra fields in the form, dynamically at run time, like "Car Milage", "Cal Manufacturer".
Suppose if the user wants to add a "Truck", he will add "Load that can be carried", "Permit" etc.
How do I achieve this in django?
There are two questions here:
How to provide a form where the user can add new fields at run time?
How to add the fields to the database so that it can be retrieved/queried later?
There are a few approaches:
key/value model (easy, well supported)
JSON data in a TextField (easy, flexible, can't search/index easily)
Dynamic model definition (not so easy, many hidden problems)
It sounds like you want the last one, but I'm not sure it's the best for you. Django is very easy to change/update, if system admins want extra fields, just add them for them and use south to migrate. I don't like generic key/value database schemas, the whole point of a powerful framework like Django is that you can easily write and rewrite custom schemas without resorting to generic approaches.
If you must allow site users/administrators to directly define their data, I'm sure others will show you how to do the first two approaches above. The third approach is what you were asking for, and a bit more crazy, I'll show you how to do. I don't recommend using it in almost all cases, but sometimes it's appropriate.
Dynamic models
Once you know what to do, this is relatively straightforward. You'll need:
1 or 2 models to store the names and types of the fields
(optional) An abstract model to define common functionality for your (subclassed) dynamic models
A function to build (or rebuild) the dynamic model when needed
Code to build or update the database tables when fields are added/removed/renamed
1. Storing the model definition
This is up to you. I imagine you'll have a model CustomCarModel and CustomField to let the user/admin define and store the names and types of the fields you want. You don't have to mirror Django fields directly, you can make your own types that the user may understand better.
Use a forms.ModelForm with inline formsets to let the user build their custom class.
2. Abstract model
Again, this is straightforward, just create a base model with the common fields/methods for all your dynamic models. Make this model abstract.
3. Build a dynamic model
Define a function that takes the required information (maybe an instance of your class from #1) and produces a model class. This is a basic example:
from django.db.models.loading import cache
from django.db import models
def get_custom_car_model(car_model_definition):
""" Create a custom (dynamic) model class based on the given definition.
"""
# What's the name of your app?
_app_label = 'myapp'
# you need to come up with a unique table name
_db_table = 'dynamic_car_%d' % car_model_definition.pk
# you need to come up with a unique model name (used in model caching)
_model_name = "DynamicCar%d" % car_model_definition.pk
# Remove any exist model definition from Django's cache
try:
del cache.app_models[_app_label][_model_name.lower()]
except KeyError:
pass
# We'll build the class attributes here
attrs = {}
# Store a link to the definition for convenience
attrs['car_model_definition'] = car_model_definition
# Create the relevant meta information
class Meta:
app_label = _app_label
db_table = _db_table
managed = False
verbose_name = 'Dynamic Car %s' % car_model_definition
verbose_name_plural = 'Dynamic Cars for %s' % car_model_definition
ordering = ('my_field',)
attrs['__module__'] = 'path.to.your.apps.module'
attrs['Meta'] = Meta
# All of that was just getting the class ready, here is the magic
# Build your model by adding django database Field subclasses to the attrs dict
# What this looks like depends on how you store the users's definitions
# For now, I'll just make them all CharFields
for field in car_model_definition.fields.all():
attrs[field.name] = models.CharField(max_length=50, db_index=True)
# Create the new model class
model_class = type(_model_name, (CustomCarModelBase,), attrs)
return model_class
4. Code to update the database tables
The code above will generate a dynamic model for you, but won't create the database tables. I recommend using South for table manipulation. Here are a couple of functions, which you can connect to pre/post-save signals:
import logging
from south.db import db
from django.db import connection
def create_db_table(model_class):
""" Takes a Django model class and create a database table, if necessary.
"""
table_name = model_class._meta.db_table
if (connection.introspection.table_name_converter(table_name)
not in connection.introspection.table_names()):
fields = [(f.name, f) for f in model_class._meta.fields]
db.create_table(table_name, fields)
logging.debug("Creating table '%s'" % table_name)
def add_necessary_db_columns(model_class):
""" Creates new table or relevant columns as necessary based on the model_class.
No columns or data are renamed or removed.
XXX: May need tweaking if db_column != field.name
"""
# Create table if missing
create_db_table(model_class)
# Add field columns if missing
table_name = model_class._meta.db_table
fields = [(f.column, f) for f in model_class._meta.fields]
db_column_names = [row[0] for row in connection.introspection.get_table_description(connection.cursor(), table_name)]
for column_name, field in fields:
if column_name not in db_column_names:
logging.debug("Adding field '%s' to table '%s'" % (column_name, table_name))
db.add_column(table_name, column_name, field)
And there you have it! You can call get_custom_car_model() to deliver a django model, which you can use to do normal django queries:
CarModel = get_custom_car_model(my_definition)
CarModel.objects.all()
Problems
Your models are hidden from Django until the code creating them is run. You can however run get_custom_car_model for every instance of your definitions in the class_prepared signal for your definition model.
ForeignKeys/ManyToManyFields may not work (I haven't tried)
You will want to use Django's model cache so you don't have to run queries and create the model every time you want to use this. I've left this out above for simplicity
You can get your dynamic models into the admin, but you'll need to dynamically create the admin class as well, and register/reregister/unregister appropriately using signals.
Overview
If you're fine with the added complication and problems, enjoy! One it's running, it works exactly as expected thanks to Django and Python's flexibility. You can feed your model into Django's ModelForm to let the user edit their instances, and perform queries using the database's fields directly. If there is anything you don't understand in the above, you're probably best off not taking this approach (I've intentionally not explained what some of the concepts are for beginners). Keep it Simple!
I really don't think many people need this, but I have used it myself, where we had lots of data in the tables and really, really needed to let the users customise the columns, which changed rarely.
Database
Consider your database design once more.
You should think in terms of how those objects that you want to represent relate to each other in the real world and then try to generalize those relations as much as you can, (so instead of saying each truck has a permit, you say each vehicle has an attribute which can be either a permit, load amount or whatever).
So lets try it:
If you say you have a vehicle and each vehicle can have many user specified attributes consider the following models:
class Attribute(models.Model):
type = models.CharField()
value = models.CharField()
class Vehicle(models.Model):
attribute = models.ManyToMany(Attribute)
As noted before, this is a general idea which enables you to add as much attributes to each vehicle as you want.
If you want specific set of attributes to be available to the user you can use choices in the Attribute.type field.
ATTRIBUTE_CHOICES = (
(1, 'Permit'),
(2, 'Manufacturer'),
)
class Attribute(models.Model):
type = models.CharField(max_length=1, choices=ATTRIBUTE_CHOICES)
value = models.CharField()
Now, perhaps you would want each vehicle sort to have it's own set of available attributes. This can be done by adding yet another model and set foreign key relations from both Vehicle and Attribute models to it.
class VehicleType(models.Model):
name = models.CharField()
class Attribute(models.Model):
vehicle_type = models.ForeigngKey(VehicleType)
type = models.CharField()
value = models.CharField()
class Vehicle(models.Model):
vehicle_type = models.ForeigngKey(VehicleType)
attribute = models.ManyToMany(Attribute)
This way you have a clear picture of how each attribute relates to some vehicle.
Forms
Basically, with this database design, you would require two forms for adding objects into the database. Specifically a model form for a vehicle and a model formset for attributes. You could use jQuery to dynamically add more items on the Attribute formset.
Note
You could also separate Attribute class to AttributeType and AttributeValue so you don't have redundant attribute types stored in your database or if you want to limit the attribute choices for the user but keep the ability to add more types with Django admin site.
To be totally cool, you could use autocomplete on your form to suggest existing attribute types to the user.
Hint: learn more about database normalization.
Other solutions
As suggested in the previous answer by Stuart Marsh
On the other hand you could hard code your models for each vehicle type so that each vehicle type is represented by the subclass of the base vehicle and each subclass can have its own specific attributes but that solutions is not very flexible (if you require flexibility).
You could also keep JSON representation of additional object attributes in one database field but I am not sure this would be helpfull when querying attributes.
Here is my simple test in django shell- I just typed in and it seems work fine-
In [25]: attributes = {
"__module__": "lekhoni.models",
"name": models.CharField(max_length=100),
"address": models.CharField(max_length=100),
}
In [26]: Person = type('Person', (models.Model,), attributes)
In [27]: Person
Out[27]: class 'lekhoni.models.Person'
In [28]: p1= Person()
In [29]: p1.name= 'manir'
In [30]: p1.save()
In [31]: Person.objects.a
Person.objects.aggregate Person.objects.all Person.objects.annotate
In [32]: Person.objects.all()
Out[33]: [Person: Person object]
It seems very simple- not sure why it should not be a considered an option- Reflection is very common is other languages like C# or Java- Anyway I am very new to django things-
Are you talking about in a front end interface, or in the Django admin?
You can't create real fields on the fly like that without a lot of work under the hood. Each model and field in Django has an associated table and column in the database. To add new fields usually requires either raw sql, or migrations using South.
From a front end interface, you could create pseudo fields, and store them in a json format in a single model field.
For example, create an other_data text field in the model. Then allow users to create fields, and store them like {'userfield':'userdata','mileage':54}
But I think if you're using a finite class like vehicles, you would create a base model with the basic vehicle characteristics, and then create models that inherits from the base model for each of the vehicle types.
class base_vehicle(models.Model):
color = models.CharField()
owner_name = models.CharField()
cost = models.DecimalField()
class car(base_vehicle):
mileage = models.IntegerField(default=0)
etc

Foreign keys in django admin list display

If a django model contains a foreign key field, and if that field is shown in list mode, then it shows up as text, instead of displaying a link to the foreign object.
Is it possible to automatically display all foreign keys as links instead of flat text?
(of course it is possible to do that on a field by field basis, but is there a general method?)
Example:
class Author(models.Model):
...
class Post(models.Model):
author = models.ForeignKey(Author)
Now I choose a ModelAdmin such that the author shows up in list mode:
class PostAdmin(admin.ModelAdmin):
list_display = [..., 'author',...]
Now in list mode, the author field will just use the __unicode__ method of the Author class to display the author. On the top of that I would like a link pointing to the url of the corresponding author in the admin site. Is that possible?
Manual method:
For the sake of completeness, I add the manual method. It would be to add a method author_link in the PostAdmin class:
def author_link(self, item):
return '%s' % (item.id, unicode(item))
author_link.allow_tags = True
That will work for that particular field but that is not what I want. I want a general method to achieve the same effect. (One of the problems is how to figure out automatically the path to an object in the django admin site.)
I was looking for a solution to the same problem and ran across this question... ended up solving it myself. The OP might not be interested anymore but this could still be useful to someone.
from functools import partial
from django.forms import MediaDefiningClass
class ModelAdminWithForeignKeyLinksMetaclass(MediaDefiningClass):
def __getattr__(cls, name):
def foreign_key_link(instance, field):
target = getattr(instance, field)
return u'%s' % (
target._meta.app_label, target._meta.module_name, target.id, unicode(target))
if name[:8] == 'link_to_':
method = partial(foreign_key_link, field=name[8:])
method.__name__ = name[8:]
method.allow_tags = True
setattr(cls, name, method)
return getattr(cls, name)
raise AttributeError
class Book(models.Model):
title = models.CharField()
author = models.ForeignKey(Author)
class BookAdmin(admin.ModelAdmin):
__metaclass__ = ModelAdminWithForeignKeyLinksMetaclass
list_display = ('title', 'link_to_author')
Replace 'partial' with Django's 'curry' if not using python >= 2.5.
I don't think there is a mechanism to do what you want automatically out of the box.
But as far as determining the path to an admin edit page based on the id of an object, all you need are two pieces of information:
a) self.model._meta.app_label
b) self.model._meta.module_name
Then, for instance, to go to the edit page for that model you would do:
'../%s_%s_change/%d' % (self.model._meta.app_label, self.model._meta.module_name, item.id)
Take a look at django.contrib.admin.options.ModelAdmin.get_urls to see how they do it.
I suppose you could have a callable that takes a model name and an id, creates a model of the specified type just to get the label and name (no need to hit the database) and generates the URL a above.
But are you sure you can't get by using inlines? It would make for a better user interface to have all the related components in one page...
Edit:
Inlines (linked to docs) allow an admin interface to display a parent-child relationship in one page instead of breaking it into two.
In the Post/Author example you provided, using inlines would mean that the page for editing Posts would also display an inline form for adding/editing/removing Authors. Much more natural to the end user.
What you can do in your admin list view is create a callable in the Post model that will render a comma separated list of Authors. So you will have your Post list view showing the proper Authors, and you edit the Authors associated to a Post directly in the Post admin interface.
See https://docs.djangoproject.com/en/stable/ref/contrib/admin/#admin-reverse-urls
Example:
from django.utils.html import format_html
def get_admin_change_link(app_label, model_name, obj_id, name):
url = reverse('admin:%s_%s_change' % (app_label, model_name),
args=(obj_id,))
return format_html('%s' % (
url, unicode(name)
))