Django, ForeignKey to non unique field? - django

I'd like to have a reference field (normally it is a foreignkey field)
parent field is the reference field.
Following is the simplified models to show what I'm trying to do.
For a given class Foo, I'd like to create another class FooCopy that can hold many copies of Foo.
(FooCopy.id, FooCopy.user_edit) pair is unique.
class Foo(Base):
parent = models.ForeignKey(
'self',
null=True, blank=True
)
class FooCopy(models.Model):
_id = models.AutoField(primary_key=True)
id = models.IntegerField(blank=True, null=True, db_index=True)
user_edit = models.ForeignKey(settings.AUTH_USER_MODEL)
parent = models.ForeignKey(
'self',
null=True, blank=True,
to_field='id',
db_constraint=False,
)
foo = Foo.objects.create()
foo_data = model_to_dict(foo)
foo_copy1 = Foo.objects.create(user_edit=user1, **foo_data)
foo_copy2 = Foo.objects.create(user_edit=user2, **foo_data)
def model_to_dict(obj, exclude=[]):
data = {}
for f in obj.__class__._meta.get_fields():
if f.name in exclude:
continue
if f.one_to_many:
continue
if isinstance(f, ForeignKey):
field_name = "{}_id".format(f.name)
else:
field_name = f.name
data[field_name] = getattr(obj, field_name)
return data
I'm getting an error saying Foo.id needs to be unique.
(FooCopy.id must set unique=True because it is referenced by a foreign key.)
Is there a relational field I could use to reference another django model instance without the restriction above? (Or could I get away with it somehow?)
All I need is the ability to use foo and foo_id, I don't need the referential integrity of ForeignKey.
-- edit
After reading Daniel Roseman's comments I think I can have
parent_id = models.IntegerField()
#property
def parent(self):
return self._default_manager.get(id=parent_id, user_edit=self.user_edit)
Although I will probably miss some of stuff that django provides for foreignkey such as parent__name etc, I'm not sure if there's a better way of doing this.

Related

Django Models relation with primary key add extra "_id" to the column

These are my two models, when I try to open City page on Django I get an error: "column city.country_id_id does not exist". I don't know why python adds extra _id there.
class Country(models.Model):
country_id = models.CharField(primary_key=True,max_length=3)
country_name = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'country'
class City(models.Model):
city_id=models.CharField(primary_key=True,max_length=3)
city_name=models.CharField(max_length=30, blank=True, null=True)
country_id = models.ForeignKey(Country, on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'city'
Because if you construct a foreign key, Django will construct a "twin field" that stores the primary key of the object. The foreign key itself is thus more a "proxy" field that fetches the object.
Therefore you normally do not add an _id suffix to the ForeignKey:
class City(models.Model):
city_id = models.CharField(primary_key=True,max_length=3)
city_name = models.CharField(max_length=30, blank=True, null=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'city'
It however might be better for unmanaged tables, to specify a db_column=… parameter [Djang-doc] in the ForeignKey:
class City(models.Model):
city_id = models.CharField(primary_key=True,max_length=3)
city_name = models.CharField(max_length=30, blank=True, null=True)
country = models.ForeignKey(Country, db_column='country_id', on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'city'
With this parameter you make it explicit how the column is named at the database side.
this is due to Django's behind the scenes magic.
The fields documentation is very clear about that and I highly recommend you read the Foreign Key section in the link below:
https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ForeignKey
Basically, when you want to access the Country reference in the if a City instance, you would do it like this:
city.country_id
I also recommend another naming convention for your Foreign Key fields. Instead of <modelname>_id = models.ForeignKey... just call it <modelname> = models.ForeignKey...
Hope this helps, happy coding

How to set foreign key to a field of another model?

I want to set a foreign key to a field of another model.
I have tried Foreign Key to_field='field_name'
class Banks(models.Model):
name = models.TextField()
id = models.IntegerField(unique=True)
class Meta:
db_table = 'banks'
class Branches(models.Model):
ifsc = models.CharField(max_length=20, null=False)
bank_id = models.ForeignKey(Banks, to_field='id', on_delete=models.CASCADE)
branch = models.CharField(max_length=50)```
ProgrammingError: column branches.id does not exist
LINE 1: SELECT "branches"."id", "branches"."ifsc", "branches"."bank_...
Just add unique=True in the name column, in the Banks model.
class Banks(models.Model):
name = models.TextField(unique=True) # Just add unique=True
id = models.IntegerField(unique=True, primary_key=True)
class Meta:
db_table = 'banks'
class Branches(models.Model):
ifsc = models.CharField(max_length=20, null=False)
bank_id = models.ForeignKey(Banks, to_field='id', on_delete=models.CASCADE) # Now it will work
branch = models.CharField(max_length=50)
This problem is not caused by the foreign key. The error is happening in the Branches model, which presumably also has a db_table Meta attribute and is based on a legacy table.
You must define a primary key for your models. If you don't, Django will do so automatically and call it id. In the case of your Banks model, you should set that id field as primary_key=True - or indeed remove it completely, since that is the default. You need to find a suitable pk for Branches as well and declare it in the field.
For your actual question, you don't need to do anything; Django will automatically set the FK to point to the PK of the target model.
class Bank(models.Model):
# removed `id` as that is the default PK
name = models.TextField()
class Meta:
db_table = 'banks'
class Branch(models.Model):
ifsc = models.CharField(max_length=20, primary_key=True) # assume this is the PK
bank = models.ForeignKey(Bank, on_delete=models.CASCADE)
branch = models.CharField(max_length=50)
class Meta:
db_table = 'branches'
Note also, since these are legacy tables you probably want to add managed = False to both Meta classes. And as suggested by AKX, it is Django style to make model names singular; you can do that without affecting the table name since that is declared explicitly.

I am working with Django, During inserting data into database i caught such error

I'm working with django, during inserting data into tables the error is generates as given below...
Error:
int() argument must be a string, a bytes-like object or a number, not 'Tbl_rule_category', How can we solve such error?
view.py
dataToRuleCtgry = Tbl_rule_category(category=category, created_by="XYZ",created_date=datetime.date.today())
dataToRuleCtgry.save()
dataToRule = Tbl_rule(rule_name=rule_name, closure=closure,category_id=Tbl_rule_category.objects.latest('category_id'), created_by="XYZ",created_date=datetime.date.today(), updated_by="XYZ", updated_date=datetime.date.today(), rule_type=rule_type, fk_tbl_rule_tbl_rule_category_id=Tbl_rule_category.objects.latest('category_id'))
dataToRule.save()
models.py
class Tbl_rule_category(models.Model):
category_id = models.AutoField(primary_key=True)
category = models.CharField(max_length=50)
created_by = models.CharField(max_length=50)
created_date = models.DateField(auto_now_add=True)
def __str__(self):
pass # return self.category, self.created_by
class Tbl_rule(models.Model):
rule_id = models.AutoField(primary_key=True)
rule_name = models.CharField(max_length=50)
closure = models.CharField(max_length=50)
category_id = models.IntegerField()
created_by = models.CharField(max_length=50)
created_date = models.DateField(auto_now_add=True)
updated_by = models.CharField(max_length=50)
updated_date = models.DateField(auto_now=True)
rule_type = models.CharField(max_length=50)
fk_tbl_rule_tbl_rule_category_id = models.ForeignKey(Tbl_rule_category,on_delete=models.CASCADE, related_name='fk_tbl_rule_tbl_rule_category_id_r')
def __str__(self):
return self.rule_name, self.closure, self.created_by, self.updated_by, self.rule_type
The error is occurring because the following is trying to add an object into an integer field: category_id=Tbl_rule_category.objects.latest('category_id')
You could just add: category_id=dataToRuleCtgry.get('category_id') or category_id=dataToRuleCtgry.category_id which will solve the error.
You also don't need to add: created_date=datetime.date.today() because your model defines auto_now=true.
As mentioned you should also amend the def __str__(self): to return a string.
https://docs.djangoproject.com/en/2.0/ref/models/instances/#django.db.models.Model.str
Alternatively
You could just add the object link directly to your foreign key for the category model.fk_tbl_rule_tbl_rule_category_id=dataToRuleCtgry. You would no longer need the integer field category_id.
It would be better practice to use the model field name category_id instead of fk_tbl_rule_tbl_rule_category_id. This would mean deleting category_id and then rename fk_tbl_rule_tbl_rule_category_id to category_id.
In Django, the ORM takes care of the basic database details for you; which means in your code you really don't have to worry about individual row ids for maintaining foreign key relationships.
In fact, Django automatically assigns primary keys to all your objects so you should concentrate on fields that are relevant to your application.
You also don't have to worry about naming fields in the database, again Django will take care of that for you - you should create objects that have fields that are meaningful to users (that includes you as a programmer of the system) and not designed for databases.
Each Django model class represents a object in your system. So you should name the classes as you would name the objects. User and not tbl_user. The best practice is to use singular names. Django already knows how to create plural names, so if you create a model class User, django will automatically display Users wherever it makes sense. You can, of course, customize this behavior.
Here is how you should create your models (we will define __str__ later):
class RuleCategory(models.Model):
name = models.CharField(max_length=50)
created_by = models.CharField(max_length=50)
created_date = models.DateField(auto_now_add=True)
class Rule(models.Model):
name = models.CharField(max_length=50)
closure = models.CharField(max_length=50)
created_by = models.CharField(max_length=50)
created_date = models.DateField(auto_now_add=True)
updated_by = models.CharField(max_length=50)
updated_date = models.DateField(auto_now=True)
rule_type = models.CharField(max_length=50)
category = models.ForeignKey(RuleCategory,on_delete=models.CASCADE)
Django will automatically create any primary or foreign key fields, and any intermediary tables required to manage the relationship between the two models.
Now, to add some records:
new_category = RuleCategory(name='My Category', created_by='XYZ')
new_category.save()
# Another way to set values
new_rule = Rule()
new_rule.name = 'Sample Rule'
new_rule.closure = closure
new_rule.created_by = 'XYZ'
new_rule.updated_by = 'XYZ'
new_rule.rule_type = rule_type
new_rule.category = new_category
new_rule.save()
Note this line new_rule.category = new_category - this is how we link two objects. Django knows that the primary key should go in the table and will take care of that automatically.
The final item is customizing the models by creating your own __str__ method - this should return some meaningful string that is meant for humans.
class RuleCategory(models.Model):
name = models.CharField(max_length=50)
created_by = models.CharField(max_length=50)
created_date = models.DateField(auto_now_add=True)
def __str__(self):
return '{}'.format(self.name)
class Rule(models.Model):
name = models.CharField(max_length=50)
closure = models.CharField(max_length=50)
created_by = models.CharField(max_length=50)
created_date = models.DateField(auto_now_add=True)
updated_by = models.CharField(max_length=50)
updated_date = models.DateField(auto_now=True)
rule_type = models.CharField(max_length=50)
category = models.ForeignKey(RuleCategory,on_delete=models.CASCADE)
def __str__(self):
return '{} for category {}'.format(self.name, self.category)
If you notice something, I just put self.category in the __str__ for the Rule model. This is because we have already defined a __str__ for the RuleCategory model, which just returns the category name; so now when we print our Rule we created, we will get Sample Rule for category My Category as a result.

Accessing an attribute of a foreignkey field in a django model

I'm trying to create a model method that returns an attribute of a foreign key. In this case, I want get_place_name() to return the pretty_name field of the place model. But when I do so, I get an attribute error: "'NoneType' object has no attribute 'pretty_name'"
class CalendarEvent(models.Model):
event_id = models.CharField( max_length=22, db_index=True, unique=True )
event_name = models.CharField( max_length=255, db_index=True )
place = models.ForeignKey( Place, blank=True, null=True )
def __unicode__(self):
return self.event_name
def get_place_name(self):
return "%s" % self.place.pretty_name
Well, you have null=True for your place foreign key. Is it possible that for the particular CalendarEvent that you are calling, place is indeed None? In which case the error would be accurate. You could handle that more gracefully by modifying your get_place_name method:
def get_place_name(self):
if self.place:
return self.place.pretty_name

Django: How to save data to ManyToManyField?

I have a question concerning the following model. I want to populate the ManyToManyField from views.py instead of doing it from the Admin.
But how do I add data to the genres field which is the ManyToManyField?
views.py
content = Movie_Info(id = m_id,
title = data[0].get('title'),
overview = data[0].get('overview'),
release_date = data[0].get('release_date'),
)
content.save()
models.py
class Movie_Info_genre(models.Model):
genre = models.CharField(max_length=100)
class Movie_Info(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=100, blank=True, null=True)
overview = models.TextField(blank=True, null=True)
release_date = models.CharField(max_length=10, blank=True, null=True)
genres = models.ManyToManyField(Movie_Info_genre)
Use the add method for related fields:
# using Model.object.create is a shortcut to instantiating, then calling save()
myMoveInfo = Movie_Info.objects.create(title='foo', overview='bar')
myMovieGenre = Movie_Info_genre.objects.create(genre='horror')
myMovieInfo.genres.add(myMoveGenre)
Unlike modifying other fields, both models must exist in the database prior to doing this, so you must call save before adding the many-to-many relationship. Since add immediately affects the database, you do not need to save afterwards.