DRY fields in Django models - django

I know about using inheritance and abstract models in reusing common fields in different models.
I'd like to know if the following approach is possible instead of inheritance and mixins.
from django.db import models
common_modified_by = models.CharField()
class Author(models.Model):
name = models.CharField()
modified_by = common_modified_by
class Book(models.Model):
title = models.CharField()
modified_by = common_modified_by
Will the above code work? Why or why not?

The issue with reusing the same field in multiple models is that the model attribute of the field will be set to the last model where the field is defined
from django.db import models
common_modified_by = models.CharField(max_length=20)
class Author(models.Model):
name = models.CharField(max_length=20)
modified_by = common_modified_by
class Book(models.Model):
title = models.CharField(max_length=20)
modified_by = common_modified_by
The field now has Book as it's model even when you get the field from the Author model. This can be problematic
>>> Book._meta.get_field('modified_by').model
<class 'foo.models.Book'>
>>> Author._meta.get_field('modified_by').model
<class 'foo.models.Book'>
One issue could be using the field as the target of a foreign key django.db.models.fields.related seems to use this model attribute quite a lot
It's also used when generating subqueries

Related

How to add a field to model with a decorator?

I'd like to add foreign keys to virtually every model in my app to allow different Sites. For this I think it would be nice to use decorators, because I will be able to only add those FKs under certain conditions (e.g. if Sites is installed).
Unfortunately my attempt doesn't do anything:
def add_site_fk_field(model_to_annotate: models.Model):
"""Add FK to Site to Model"""
model_to_annotate.site = models.ForeignKey(Site, on_delete=models.CASCADE)
return model_to_annotate
#add_site_fk_field
class Test(models.Model):
...
Is it somehow possible to do this? I prefet not to use abstract classes.
As I know you cant do it with decorator. But you can do that with creating an abstract base model class.
For example:
from django.db import models
class Site(models.Model):
name = models.CharField(max_length=60)
class AddSiteFkField(models.Model):
fk_site = models.ForeignKey(Site, on_delete=models.CASCADE)
class Meta:
abstract = True
class Test(AddSiteFkField):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)

Exporting a field from another model, using foreign key

I've just set up the whole import-export thing and I just can't make it export a field from another model, using the foreign key.
models.py
from django.db import models
from django.contrib.auth.models import User
from datetime import date
from .validators import validate_file_size
# Create your models here.
class CORMeserii(models.Model):
CodCOR = models.CharField(max_length=25, primary_key=True, unique=True)
MeserieCor = models.CharField(max_length=50, unique=True)
def __str__(self):
return str(self.CodCOR + " - " + self.MeserieCor)
class Meta:
verbose_name_plural = "CORuri"
class Oferta(models.Model):
solicitant = models.ForeignKey(User, on_delete=models.CASCADE)
cor = models.ForeignKey(CORMeserii, on_delete=models.CASCADE)
dataSolicitare = models.DateField(default=date.today)
locuri = models.IntegerField()
agentEconomic = models.CharField(max_length=50)
adresa = models.CharField(max_length=150)
dataExpirare = models.DateField()
experientaSolicitata = models.CharField(max_length=200)
studiiSolicitate = models.CharField(max_length=200)
judet = models.CharField(max_length=20)
localitate = models.CharField(max_length=25)
telefon = models.CharField(max_length=12)
emailContact = models.EmailField(max_length=40)
rezolvata = models.BooleanField(default=False)
def __str__(self):
return str(self.cor)
admin.py
from django.contrib import admin
from .models import Oferta, CORMeserii
from import_export import resources
from import_export.admin import ImportExportMixin, ImportExportModelAdmin
import tablib
# Register your models here.
class CorImEx(resources.ModelResource):
class Meta:
model = CORMeserii
class CorAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('CodCOR', 'MeserieCor')
resource_class = CorImEx
class CorImExAdmin(ImportExportModelAdmin):
resource_class = CorImEx
class OferteImEx(resources.ModelResource):
class Meta:
model = Oferta
fields = ('id', 'solicitant', 'cor', 'oferta.cor.MeserieCor')
class OfertaAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('id', 'solicitant', 'dataExpirare', 'dataSolicitare')
resource_class = OferteImEx
class OferteImExAdmin(ImportExportModelAdmin):
resource_class = OferteImEx
admin.site.register(Oferta, OfertaAdmin)
admin.site.register(CORMeserii, CorAdmin)
You can see it in the OferteImEx class - the field 'oferta.cor.MeserieCor'.
I want to export the MeserieCor field from the model CORMeserii - but I can't figure it out.
I tried with: 'oferta.cor.MeserieCor',
cor.MeserieCor', 'MeserieCor' (even though the last 2 ones don't make sense to me at all).
Is there any way to export that field somehow, even though it is from another model? (I'm pretty sure there is but I can't figure it out)
Thanks.
In Django you use double underscore (__) to follow relationship in lookups. It's in the documentation:
Django offers a powerful and intuitive way to “follow” relationships in lookups, taking care of the SQL JOINs for you automatically, behind the scenes. To span a relationship, just use the field name of related fields across models, separated by double underscores, until you get to the field you want.
Link: Lookups that span relationship
There is even an example in the django import export documentation how to follow relationship:
When defining ModelResource fields it is possible to follow model relationships:
class BookResource(resources.ModelResource):
class Meta:
model = Book
fields = ('author__name',)
Source: https://django-import-export.readthedocs.io/en/latest/getting_started.html#customize-resource-options

What is the process that you follow to create model in Django?

What is the process that you follow to create model in Django? Thanks.
The most important part of a model – and the only required part of a model – is the list of database fields it defines. Fields are specified by class attributes. Be careful not to choose field names that conflict with the models API like clean, save, or delete.
Models.py
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
You can start here Documentation
See also Django Girls Models

Django models asymmetric relationship

With following Django models:
class Author(models.Model):
name = models.CharField(max_length=30)
bestbookaccordingtome=models.????(Author,null=True, blank=True, default = None)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.OnetoOneField(Author)
I want the classic relationship each book has one author (only).
But I also want to be able to assign a chosen book to author (my favourite book of this author for example).
I tried a foreign key but django didn't like it.
Any python clean way to do it?
What you need is ManyToManyField.
class Author(models.Model):
name = models.CharField(max_length=30)
bestbookaccordingtome = models.ManyToManyField('self', symmetrical=False, related_name='best_book_according_to_me')
Also, if you need to specify extra fields in your n-m model, you can use through to indicate the name of the model.
Hope it helps!

Django 1.2.1 Inline Admin for Many To Many Fields

I recently upgraded to Django 1.2.1 because I was specifically interested in the ability to have basic many-to-many inline fields. When using the admin like so:
Initial models:
class Ingredient(models.Model):
name = models.TextField()
class Recipe(models.Model):
ingredients = models.ManyToManyField(Ingredient)
Initial admin:
class IngredientInline(admin.TabularInline):
model = Recipe.ingredients.through
class RecipeOptions(admin.ModelAdmin):
inlines = [IngredientInline,]
exclude = ('ingredients',)
admin.site.register(Recipe,RecipeOptions)
What I got was the same form you would normally see on a ManyToMany field, with some extra rows. Supplying it with extra parameters like an Ingredient ModelForm did not help. Suspecting that something might be wrong with the basic ModelForm associations via model = Foo.manyfields.through, I decided to see if an intermediary model would help. It now displays a working inline form via:
New models:
class RecipeJoin(models.Model):
pass
class Recipe(models.Model):
ingredients = models.ManyToManyField(RecipeJoin,through='Ingredient')
class Ingredient(models.Model):
name = models.TextField()
test = models.ForeignKey(RecipeJoin,null=True,blank=True,editable=False)
New admin:
class IngredientInline(admin.TabularInline):
model = Recipe.ingredients.through
class RecipeOptions(admin.ModelAdmin):
inlines = [IngredientInline,]
admin.site.register(Recipe,RecipeOptions)
Obviously this is not a hack I'd like to use. Anyone know of a way to get a manytomany relationship to display via inline form without either (a) creating an entirely new BasicInline form and template or (b) putting it through an intermediary (or generic admin) model?
TIA. (I apologize for verbosity, it's my first post so wanted to be thorough).
Do one of these examples accomplish what you are trying to do?
a:
# Models:
class Ingredient(models.Model):
name = models.CharField(max_length=128)
class Recipe(models.Model):
name = models.CharField(max_length=128)
ingredients = models.ManyToManyField(Ingredient, through='RecipeIngredient')
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
amount = models.CharField(max_length=128)
# Admin:
class RecipeIngredientInline(admin.TabularInline):
model = Recipe.ingredients.through
class RecipeAdmin(admin.ModelAdmin):
inlines = [RecipeIngredientInline,]
class IngredientAdmin(admin.ModelAdmin):
pass
admin.site.register(Recipe,RecipeAdmin)
admin.site.register(Ingredient, IngredientAdmin)
b:
# Models:
class Recipe(models.Model):
name = models.CharField(max_length=128)
class Ingredient(models.Model):
name = models.CharField(max_length=128)
recipe = models.ForeignKey(Recipe)
# Admin:
class IngredientInline(admin.TabularInline):
model = Ingredient
class RecipeAdmin(admin.ModelAdmin):
inlines = [IngredientInline,]
admin.site.register(Recipe,RecipeAdmin)
If I remember correctly (and it's been a while since I've done this part), you need to add the admin for Ingredient and set it to have the custom ModelForm. Then that form will be used in the inline version of Ingredient.