How to change the primary key of manytomany table in django - django

I am changing the primary key of the legacy database. I was able to change the primary key by setting id as the primary key.
Before
class User(models.Model):
name = models.CharField(max_length=5)
email = models.CharField(max_length=5)
age = models.CharField(max_length=5)
After
class User(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.CharField(max_length=5)
email = models.CharField(max_length=5)
age = models.CharField(max_length=5)
Then
python manage.py makemigrations
python manage.py migrate
This is working fine.
But I also want to change the default primary key of the tables created via ManyToMany feild.
User Model
class User(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.CharField(max_length=5)
email = models.CharField(max_length=5)
age = models.CharField(max_length=5)
UserProfile Model
class UserProfile(models.Model):
id = models.BigIntegerField(primary_key=True)
address = models.CharField(max_length=5)
father_name = models.CharField(max_length=5)
pincode = models.CharField(max_length=5)
user = models.ManyToManyField(User)
The ManytoMany field creates table called User_user_userprofile with id as Autofield basically previous or default django primary key.
id, user_id, userprofile_id
ManytoMany Table
Now, How to change the primarykey of ManytoMany Feild ie id created by Django?
PS:
Django: 1.11
Python: 2.7.5
DB: Sqlite3 3.7.17 2013-05-20

I stumbled upon this problem today, and ended up solving it by using the through argument of the ManyToManyField. I solved it for Django v3.2.6 however, but the documentation for v1.11 mentions the same behavior for the same argument, so hopefully the solution should work for your version of Django too. Here's the link to the documentation for v1.11 ManyToManyField.through
What the through argument allows you to do is to create the intermediary table (created automatically by ManyToManyField) yourself. You get finer control of how the intermediary table should look like, what fields it should have and what their behavior should be. Hope you are getting a picture.
Let me give you the example of the problem I faced and how I solved it. Hopefully that will make this clearer.
I was trying to establish a many-to-many relationship between two of my existing models.
My first model looks like this,
class BanglaWords(models.Model):
class Meta:
verbose_name_plural = 'Bangla Words'
bng_id = models.CharField(max_length=16, primary_key=True)
bangla_word = models.CharField(max_length=64)
def __str__(self):
return self.bangla_word
and the second one looks like,
class EnglishWords(models.Model):
class Meta:
verbose_name_plural = 'English Words'
eng_id = models.IntegerField(primary_key=True)
word = models.CharField(max_length=64)
bangla_word = models.ManyToManyField(BanglaWords)
def __str__(self):
return self.word
But this resulted in an intermediary table wordnet_englishwords_bangla_word which looked like this,
wordnet_englishwords_bangla_word
id
englishwords_id
banglawords_id
But I didn't want this, I wanted bng_id to be the pk for this table. I solved the problem with ManyToManyField.through as follows,
I defined the intermediary model(table) myself and with the through argument, I pointed to the new intermediary model I created and instructed django to create the table the way I wanted it.
First I created the intermediary model,
class BanglaEnglishRelations(models.Model):
class Meta:
verbose_name_plural = 'Bangla English Relations'
bng_id = models.OneToOneField('BanglaWords', primary_key=True, on_delete=models.CASCADE)
eng_id = models.ForeignKey('EnglishWords', on_delete=models.CASCADE)
which defines bng_id as the primary key as I desired.
Second, I told the ManyToManyField in EnglishWords to base the table on BanglaEnglishRelations like,
bangla_word = models.ManyToManyField(BanglaWords, through=BanglaEnglishRelations)
This resulted in the table wordnet_banglaenglishrelations which looked like,
wordnet_banglaenglishrelations
bng_id_id
eng_id_id
and surved my purposes. You can do something similar to solve your problem and promote whatever field to a pk.

Related

Get all related field in Django model

I am struggling to understand how one-to-many and many-to-many relation works in Django model. My schema looks something like this so far, I am open for suggestions to make it better.
A many-to-many relation between users and team. Also, there will be schedules that belong to a particular user of a team.
This is how my model looks like so far,
class Team(models.Model):
tid = models.AutoField(primary_key=True)
team_name = models.CharField(max_length=30)
manager_name = models.CharField(max_length=30)
class Schedule(models.Model):
sid = models.AutoField(primary_key=True)
user = models.OneToOneField(User)
date = models.DateField()
start_time = models.TimeField()
end_time = models.TimeField()
pay_rate = models.CharField(max_length=30)
location = models.CharField(max_length=50)
class BelongsTo(models.Model):
bid = models.AutoField(primary_key=True)
user = models.OneToOneField(User)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
Question: I want to get the information of each user, what are their schedules and which team each schedule belongs to. How would I to do it? I have tried BelongsTo.objects.select_related().all(), but it is not working for me.
Note: I am open for suggestions, if something is wrong with my schema or model or the approach, please let me know.
BelongsTo is seems like utility table.So
BelongsTo.objects.all().values('user', 'team__team_name', 'schedule')
Your schema looks almost right, but I would modify it a little bit. In particular, I will change how Schedule is implemented. Instead of having a sid in the User Belongs To join-table, I would include the user and team in the Schedule table as foreign keys.
This is how the Django models should then look like:
class User(models.Model):
username = models.CharField(max_length = 200)
# put other fields like password etc. here
class Team(models.Model):
team_name = models.CharField(max_length=30)
manager_name = models.CharField(max_length=30)
user = models.ManyToManyField("User")
class Schedule(models.Model):
user = models.ForeignKey("User")
team = models.ForeignKey("Team")
date = models.DateField()
start_time = models.TimeField()
end_time = models.TimeField()
pay_rate = models.CharField(max_length=30)
location = models.CharField(max_length=50)
Note the following:
You don't need to have a primary key field in the models because Django adds a primary key field called pk or id automatically.
Note the absence of the User Belongs To model. Django implements join-tables like User Belongs To automatically when you use a ManyToManyField. See the Django docs on many-to-many relationships.
You also don't need on_delete = models.CASCADE on ForeignKey fields, because this is the default behavior.
To see how to get information about users, teams and schedule from this configuration of models, consult the Django documentation on making db queries. It's quite easy.

Django model with Foreign Key and ManyToMany relations to same model

I have a django model as follows:
class Subscription(models.Model):
Transaction = models.ManyToManyField(Transaction, blank=True, null=True)
User = models.ForeignKey(User)
...etc...
I am trying to add a ManyToMany field to the User model as follows:
SubUsers = models.ManyToManyField(User, blank=True, null=True)
but I get this error when I run syncdb:
AssertionError: ManyToManyField(<django.db.models.fields.related.ForeignKey object at 0x19ddfd0>) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string 'self'
If I encase User in quotes, I get instead:
sales.subscription: 'User' has a relation with model User, which has either not been installed or is abstract.
I know the User model is imported correctly. Any ideas why having 2 fields pointing to the User model causes problems? Thanks in advance...
The reason why it fails is because the name of your field is the same as the class name (User). Use lowercase field names, it the standard convention in Django and Python. See Django Coding style
Also, you need to add a related_nameparameter to your relationship:
class Subscription(models.Model):
user = models.ForeignKey(User)
sub_users = models.ManyToManyField(User, blank=True, null=True, related_name="subscriptions")

Ordering a Foreign Key Field by Date Added

I have the following two models:
class Position(models.Model):
position = models.CharField(max_length=100)
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
positions = models.ManyToManyField(Position, blank=True, null=True)
This creates a database table called userprofile_userprofile_positions, with the following three columns:
id
userprofile_id
position_id
How would I add a fourth field to this table --
created_at = models.DateTimeField(auto_now_add=True)
I would like to do this through django, if possible. Thank you.
Create a new model and specify it in the through attribute of your ManyToMany. The Django docs have a section on this exact use case: Extra fields on many-to-many relationships.

Using a UUID as a primary key in Django models (generic relations impact)

For a number of reasons^, I'd like to use a UUID as a primary key in some of my Django models. If I do so, will I still be able to use outside apps like "contrib.comments", "django-voting" or "django-tagging" which use generic relations via ContentType?
Using "django-voting" as an example, the Vote model looks like this:
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
vote = models.SmallIntegerField(choices=SCORES)
This app seems to be assuming that the primary key for the model being voted on is an integer.
The built-in comments app seems to be capable of handling non-integer PKs, though:
class BaseCommentAbstractModel(models.Model):
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
object_pk = models.TextField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
Is this "integer-PK-assumed" problem a common situation for third-party apps which would make using UUIDs a pain? Or, possibly, am I misreading this situation?
Is there a way to use UUIDs as primary keys in Django without causing too much trouble?
^ Some of the reasons: hiding object counts, preventing url "id crawling", using multiple servers to create non-conflicting objects, ...
As seen in the documentation, from Django 1.8 there is a built in UUID field. The performance differences when using a UUID vs integer are negligible.
import uuid
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
You can also check this answer for more information.
A UUID primary key will cause problems not only with generic relations, but with efficiency in general: every foreign key will be significantly more expensive—both to store, and to join on—than a machine word.
However, nothing requires the UUID to be the primary key: just make it a secondary key, by supplementing your model with a uuid field with unique=True. Use the implicit primary key as normal (internal to your system), and use the UUID as your external identifier.
The real problem with UUID as a PK is the disk fragmentation and insert degradation associated with non-numeric identiifers. Because the PK is a clustered index (in virtually every RDBMS except PostgreSQL), when it's not auto-incremented, your DB engine will have to resort your physical drive when inserting a row with an id of lower ordinality, which will happen all the time with UUIDs. When you get lots of data in your DB, it may take many seconds or even minutes just to insert one new record. And your disk will eventually become fragmented, requiring periodic disk defragmentation. This is all really bad.
To solve for these, I recently came up with the following architecture that I thought would be worth sharing.
The UUID Pseudo-Primary-Key
This method allows you to leverage the benefits of a UUID as a Primary Key (using a unique index UUID), while maintaining an auto-incremented PK to address the fragmentation and insert performance degredation concerns of having a non-numeric PK.
How it works:
Create an auto-incremented primary key called pkid on your DB Models.
Add a unique-indexed UUID id field to allow you to search by a UUID id, instead of a numeric primary key.
Point the ForeignKey to the UUID (using to_field='id') to allow your foreign-keys to properly represent the Pseudo-PK instead of the numeric ID.
Essentially, you will do the following:
First, create an abstract Django Base Model
class UUIDModel(models.Model):
pkid = models.BigAutoField(primary_key=True, editable=False)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
class Meta:
abstract = True
Make sure to extend the base model instead of models.Model
class Site(UUIDModel):
name = models.CharField(max_length=255)
Also make sure your ForeignKeys point to the UUID id field instead of the auto-incremented pkid field:
class Page(UUIDModel):
site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)
If you're using Django Rest Framework (DRF), make sure to also create a Base ViewSet class to set the default search field:
class UUIDModelViewSet(viewsets.ModelViewSet):
lookup_field = 'id'
And extend that instead of the base ModelViewSet for your API views:
class SiteViewSet(UUIDModelViewSet):
model = Site
class PageViewSet(UUIDModelViewSet):
model = Page
More notes on the why and the how in this article: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps
I ran into a similar situation and found out in the official Django documentation, that the object_id doesn't have to be of the same type as the primary_key of the related model. For example, if you want your generic relationship to be valid for both IntegerField and CharField id's, just set your object_id to be a CharField. Since integers can coerce into strings it'll be fine. Same goes for UUIDField.
Example:
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.CharField(max_length=50) # <<-- This line was modified
object = generic.GenericForeignKey('content_type', 'object_id')
vote = models.SmallIntegerField(choices=SCORES)
this can be done by using a custom base abstract model,using the following steps.
First create a folder in your project call it basemodel then add a abstractmodelbase.py with the following below:
from django.db import models
import uuid
class BaseAbstractModel(models.Model):
"""
This model defines base models that implements common fields like:
created_at
updated_at
is_deleted
"""
id = models.UUIDField(primary_key=True, unique=True, default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
is_deleted = models.BooleanField(default=False)
def soft_delete(self):
"""soft delete a model instance"""
self.is_deleted=True
self.save()
class Meta:
abstract = True
ordering = ['-created_at']
second: in all your model file for each app do this
from django.db import models
from basemodel import BaseAbstractModel
import uuid
# Create your models here.
class Incident(BaseAbstractModel):
""" Incident model """
place = models.CharField(max_length=50, blank=False, null=False)
personal_number = models.CharField(max_length=12, blank=False, null=False)
description = models.TextField(max_length=500, blank=False, null=False)
action = models.TextField(max_length=500, blank=True, null=True)
image = models.ImageField(upload_to='images/', blank=True, null=True)
incident_date = models.DateTimeField(blank=False, null=False)
So the above model incident inherent all the field in baseabstract model.
The question can be rephrased as "is there a way to get Django to use a UUID for all database ids in all tables instead of an auto-incremented integer?".
Sure, I can do:
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
in all of my tables, but I can't find a way to do this for:
3rd party modules
Django generated ManyToMany tables
So, this appears to be a missing Django feature.

ForeignKey field problem in Django

I have declared two of my models this way:
class EmailAddress(models.Model):
customer = models.ForeignKey(Customer)
email_address = models.CharField(max_length=200)
def __unicode__(self):
return self.email_address
class Customer(models.Model):
.
.
.
email_address = models.ForeignKey(EmailAddress)
def __unicode__(self):
name = ''+str(self.title)+" "+str(self.first_name)+" "+str(self.last_name)
return name
The idea is that one customer can have several email addresses associated to him/her...the problem is how to do this correctly...as you can see from my code above, the customer foreign key field has to be after the customer class, but the email address foreign key field has to be after the EmailAddress class...how do I sort out this issue?
There is a serious logic flaw here - ForeignKey from Customer to Email would mean that each customer has only one email. You would want to skip that foreignkey alltogether:
class Email(models.Model):
customer = models.ForeignKey(Customer, related_name='email_addresses')
then simply do customer.email_addresses to get a list of all emails. You dont need another ForeignKey, django uses relationships defined in one model (unlike RoR and other MVC frameworks)
I don't see why you want to use a ForeignKey in EmailAddress.
Extract from Python web development with Django:
Foreign keys are generally used to
define one-to-many (or many-to-one)
relationships.
In the next example a Book has a single Author and an Author can have many Books.
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
Just add single-quotes around Customer:
class EmailAddress(models.Model):
customer = models.ForeignKey('Customer')
email_address = models.CharField(max_length=200)
def __unicode__(self):
return self.email_address
Menda's answer is correct. There isn't really an ordering problem because the Customer model doesn't need a ForeignKey field. Just remove that and flip the order in which the classes are defined.
class Customer(models.Model):
pass
class EmailAddress(models.Model):
customer = models.ForeignKey(Customer)
email_address = models.CharField(max_length=200)
There's also a Django email field you can use. See Django EmailField. Just wanted to mention that in case it could add value to you application.