Django - create unique key that contains other models field - python

I'm trying to create 'unique_together' meta for a model, but instead of two fields from the current model, one of them is a field of other model (which is a foreign key in the current model):
I want the 'Item' model to have unique_together that contains both its 'identifier' field and the Spec's container_id. a Spec is foreign key in 'Item'.
Tried something like this, but I get "Unresolved reference spec..."
class Spec(BaseModel):
title = models.CharField(max_length=200)
identifier = models.IntegerField(unique=True)
container = models.ForeignKey(Container, related_name='specs')
class Item(SubmittalsBaseModel, BaseModel, DirtyFieldsMixin):
title = models.CharField(max_length=200)
identifier = models.CharField(max_length=200, unique=True)
spec = models.ForeignKey(Spec, related_name='items')
class Meta:
container_id = spec.container
unique_together = ('identifier', 'container_id')

You can't do that.. (at least I think)..
The unique_together clause is directly translated to the SQL unique index. And you can only set those on columns of a single table, not a combination of several tables.
You can add validation for it yourself though, simply overwrite the validate_unique method and add this validation to it.
Docs: http://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.validate_unique

Related

How to override M2M field names and models in Django with an existing database?

I'm using Django 3.1.3 and working with an existing postgresql database. Most of the models and fields names of this DB are badly chosen and/or way too long. Most of the time its easy to change them with some handy Django options like so :
class NewModelName(models.Models):
new_field_name = models.CharField(max_length=50, db_column='old_field_name')
class Meta:
managed=False
db_table='database_old_table_name'
But let say I want to change a M2M field name and the corresponding model name. I'd like to have something like :
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField('RelatedModel', blank=True, db_column='old_m2m_field_name')
class Meta:
managed=False
db_table='foo_old_table_name'
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed=False
db_table='related_model_old_table_name'
But if I do that, Django will throw an error stating
django.db.utils.ProgrammingError: relation "foo_new_m2m_field_name" does not exist. It is like it is ignoring the db_column option. Any idea how I could get to a similar result ?
Thanks!
From Django documentation regarding ManyToManyField
ManyToManyField.db_table The name of the table to create for storing
the many-to-many data. If this is not provided, Django will assume a
default name based upon the names of: the table for the model defining
the relationship and the name of the field itself.
Also depending on column names (non standard names) in original database you might have to define through model ( pivot table) as through table
You will probably need to manually define the Through model (that Django would otherwise implicitly create behind the scenes) in order to make it unmanaged.
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField(
"RelatedModel",
blank=True,
db_column="old_m2m_field_name",
through="FooRelatedJoin", # <- new
)
class Meta:
managed = False
db_table = "foo_old_table_name"
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed = False
db_table = "related_model_old_table_name"
class FooRelatedJoin(models.Models): # <- all new
foo = models.ForeignKey(Foo)
related_model = models.ForeignKey(RelatedModel)
class Meta:
managed = False
db_table = "foo_join_table"
You could add a property db_table (link)
linking to the previous table, named foo_old_table_name in your case.
According to the doc,
By default, this table name is generated using the name of the
many-to-many field and the name of the table for the model that
contains it
So for the field new_m2m_field_name, the previous table making the link was named : old_field_name_database_old_table_name.
Hence :
new_field_name = models.CharField(max_length=50, db_column='old_field_name', db_table='old_field_name_database_old_table_name')
The option through could be changed too, but I do not think it is necessary if the modifications on names are coherent.

Cannot define 'through' relationship on two models which share a base class in Django

I've got a 'set' model, which has a many to many relationship to the 'Item' model. The issue is that 'set' is a subclass of Item (everything is an Item on the project).
It works fine, until I try to create a 'through' relationship to an intermediary model called 'order', which I'm defining so I can order the 'Items' inside the 'Set'.
When I try to define the relationship, I get this error:
ERRORS:
curate.Set.items_: (fields.E001) Field names must not end with an underscore.
curate.order.set: (fields.E304) Reverse accessor for 'order.set' clashes with reverse accessor for 'order.item'.
HINT: Add or change a related_name argument to the definition for 'order.set' or 'order.item'.
I've tried adding a related_name to Order.set, and also Order.item, but it doesn't seem to work. I can get migrations to be happy, but then when I try to migrate I get an error saying:
ValueError: Cannot alter field curate.Set.items into curate.Set.items - they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)
models.py
class Item(models.Model, AdminVideoMixin):
title = models.TextField(max_length=5000)
slug = models.SlugField(max_length=5000, default='')
...
class Video(Item):
video_embed = EmbedVideoField(max_length=500)
...
class Article(Item):
authors = models.CharField(max_length=10000)
...
class Podcast(Item):
categories = models.CharField(max_length=5000, null=True, blank=True,)
...
class Episode(Item):
author = models.CharField(max_length=10000, null=True,blank=True,)
...
class Set(Item):
items = models.ManyToManyField(Item, related_name='setItems', max_length=5000, through='Order')
front_page = models.BooleanField(max_length=300, blank=False, default=False, null=False)
class order(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE,)
set = models.ForeignKey(Set, on_delete=models.CASCADE,)
order = models.CharField(max_length=64, default=0)
You can't change a ManyToMany field to use a through relationship once it has been created. This is because the through table will now contain extra fields that cannot be populated by the standard ManyToMany relationship (this is the same reason why you can't use add, clear, remove, delete when using a through table). You will need to create a new ManyToMany field on Set that uses the through relationship and then populate it with the data from Set.items before removing Set.items.
Docs on through tables
If you need to have the new field called items you can do the following:
Change items to items_old in the Set model, you will need to change the related_name as well if you would like that to be the same (make and run the migration)
Add the new items field with the through relationship (make and run the migration)
Populate the new items field with the data from items_old
set_items = Set.objects.filter(items__isnull=False)
Order.objects.bulk_create([
Order(item=item, set=s)
for s in set_items
for item in s.items.all()
])
Remove items_old from Set
NB - Like katoozi mentioned in his comment, you probably shouldn't have fields named set

Django IntegrityError while updating a record

I have a model with double columns as primary key. I do a filter on it and get the records I want, change a field and save it. As I know save will update the record and does not create a new instance of the model in db. so It should be all okay but I'm stuck with an integrityError Duplicate entry '10-2' for key 'PRIMARY' when I try to save the record
Here is the code snippet:
analysis = AnalysisResult.objects.filter(request=req.request_id)
for anal in analysis:
anal.analysisresult_path = some_string
anal.save() #this line is where the error occurs
And here is my model:
class AnalysisResult(models.Model):
analysisresult_path = models.CharField(db_column='analysisResult_path', max_length=255, blank=True,
null=True) # Field name made lowercase.
detectionresult_path = models.CharField(db_column='detectionResult_path', max_length=255, blank=True,
null=True) # Field name made lowercase.
targetcode = models.ForeignKey('TagetCode', models.DO_NOTHING,
db_column='targetCode_id') # Field name made lowercase.
request = models.ForeignKey('Request', models.DO_NOTHING, primary_key=True)
class Meta:
managed = False
db_table = 'analysis_result'
unique_together = (('request', 'targetcode'),)
Ah, yes, welcome to one of django's strongest opinions: all tables/models should have a single primary key field that can be used for updates. If you don't have this, you must write raw SQL for the update since save will assume that there is an implicit primary key field called id to use in the where clause.

Django Foreign Key automatically filled based off of another field on model

So I currently have an "Account" Model and "Account Comments" Model -- keep in mind, that I can't really control the scheme of the database, and I'm writing a wrapper around existing DB.
Under the AccountComments Model, there is a field called "Data". It is where the AccountComments actually are and I'm trying to basically create a foreign key on the Account model without actually having to redesign and add a "AccountComments" field on Account that holds the AccountID.
class Account(models.Model):
AccountID = models.AutoField(editable=False, db_column='AccountID', verbose_name='ID',
primary_key=True)
acctno = models.IntegerField(editable=True, unique=True, db_column='ACCTNO', verbose_name='Acct #', blank=True,
null=True) # Field name made lowercase.
accountComments = models.ForeignKey('accountComments',to_field='accountID',db_column='Data')
def __str__(self):
return str(self.acctno)
class Meta:
managed = False
db_table = 'Account'
class accountComments(models.Model):
accountCommentID = models.AutoField(db_column='AccountCommentID', primary_key=True) # Field name made lowercase.
accountID = models.IntegerField(db_column='AccountID') # Field name made lowercase.
EntryUserID = models.IntegerField(db_column='EntryUserID') # Field name made lowercase.
EntryStamp = models.DateTimeField(db_column='EntryStamp', ) # Field name made lowercase.
Data = models.TextField(db_column='Data') # Field name made lowercase.
guid = models.CharField(db_column='guid', max_length=255) # Field name made lowercase.
class Meta:
managed = False
db_table = 'AccountComment'
ultimately, want accountComments on the Account Model to use 'AccountID' to do a lookup onto accountComments.accountID and then provide back 'Data'.
I know that I can use
def accountComments(self):
return str(accountComment.objects.get(accountID = self.AccountID).Data)
but I want it to work with Django Admin, so I need it to be an integrated part of the model.
Thanks if anyone can point me in the right direction.
You're trying to do too much with a foreign key. Following a foreign key in Django should return the model instance, not a particular field from the model instance.
The db_column should be the column in the Account table that stores the id, e.g.
accountComments = models.ForeignKey('accountComments',to_field='accountID',db_column='AccountID')
Then, to get the Data for a particular account instance, you would do:
account.accountComments.Data

Retrieve Django rest framework related fields

Using the django-rest-framework is it possible to retrieve content from a related field. So for example I want to create a genre list which contains all projects within it. This is what I have but I keep on getting the error:
'Genre' object has no attribute 'project_set'
models.py
class Genre(models.Model):
name = models.CharField(max_length=100, db_index=True)
class Project(models.Model):
title = models.CharField(max_length=100, unique=True)
genres = models.ManyToManyField(Genre, related_name='genres')
serializers.py
class GenreSerializer(serializers.ModelSerializer):
project_set = serializers.ManyRelatedField()
class Meta:
model = Genre
fields = ('name', 'project_set')
The related name you're using on the Project class is badly named. That related name is how you access the set of projects related to a given genre instance. So you should be using something like related_name='projects'. (As it is you've got it the wrong way around.)
Then make sure that your serializer class matches up with the related name you're using, so in both places project_set should then instead be projects.
(Alternatively you could just remove the related_name='genres' entirely and everything will work as you were expecting, as the default related_name will be 'project_set'.)

Categories