Set unique primary key based on foreignkey - python

I have a model defined as -
class sales_order(models.Model):
customer=models.ForeignKey()
item=models.ForeignKey()
branch=models.ForeignKey()
---
---other fields
Now for each branch, I want to start the primary key from 1 ("id" for eg.), but the default functionality of Django will increment the id irrespective of any other data.
I'm ok even if id keeps on incrementing as it does, and then I set my own field making it unique per branch and this field should auto increment without the user passing the data by checking the previous value from the database such as -
class order_serializer(serializers.ModelSerializer):
class Meta:
validators = [
UniqueTogetherValidator(
queryset=sales_order.objects.all(),
fields=['myOwnDefinedField', 'branch']
)
]
I'm in a state of no idea how to achieve this. Using Django 3.1.5.
Any help?

In the model's save method you can perform a query to get the greatest value in the field for the current branch, add 1 to this value and then save that as the new value. Only do this if there is not already a value so that we don't overwrite existing rows
Use Meta.unique_together to enforce this constraint at the DB level too
from django.db.models.functions import Coalesce
class SalesOrder(models.Model):
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
branch_unique_id = models.IntegerField(editable=False, blank=True)
class Meta:
unique_together = (
('branch', 'branch_unique_id'),
)
def save(self, *args, **kwargs):
if not self.branch_unique_id:
self.branch_unique_id = SalesOrder.objects.filter(
branch=self.branch
).aggregate(
max=Coalesce(models.Max('branch_unique_id'), 0)
)['max'] + 1
super().save(*args, **kwargs)

Related

save() saves all fields except ManyToMany field

I have a model "Contest" with one m2m field called "teams" which is related to a model "Team".
I overrided the method save. In my save() function (the one that's overriding), I need a queryset (in my save overrinding function) with all the objects related to my team m2m field. The code is self.teams.all() but it won't work because my models is not yet registered in database right ? So I call super().save(*args, **kwargs) now my models is saved and I can get my queryset ?
I can't. The queryset is empty, even if I registered team(s). <QuerySet []>
Why does super.save() save immediately all the fields except the m2m ?
I use exclusively the django admin web site to create models. No manage.py shell or form.
My model :
class Contest(models.Model):
name = models.CharField(max_length=16, primary_key=True, unique=True, default="InactiveContest", blank=True) # Ex: PSGvMNC_09/2017
id = models.IntegerField(default=1)
teams = models.ManyToManyField(Team, verbose_name="opposants")
date = models.DateTimeField(blank=True)
winner = models.ForeignKey(Team, verbose_name='gagnant', related_name='winner', on_delete=models.SET_NULL, blank=True, null=True)
loser = models.ForeignKey(Team, verbose_name='perdant', related_name='loser', on_delete=models.SET_NULL, blank=True, null=True)
bet = models.IntegerField(verbose_name='Nombre de paris', default=0, blank=True, null=0)
active = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self._state.adding:
self.active = False
# Django's id field immitation
last_id = Contest.objects.all().aggregate(Max('id')).get('id__max')
if last_id is not None:
self.id = last_id + 1
super().save(*args, **kwargs)
print(Contest.objects.get(id=self.id)) # Works --> model saved in db... in theory
queryset = self.teams.all() # Empty all the time !
Once save() (the overrinding one) has been executed one time, the problem is solved and next times (modifications) I can get my queryset so I could just use self._state.adding but this method obliges me to save 2 times (creation, editing).
I need to understand why super().save(*args, **kwargs)behaves like this and how can I solve this ?
The reason is that implementation of ManyToManyField does not use the database table of the model. It uses third table for connection between two models.
Each model has its own database table. In your example
model Contest -> table app_contest
model Team -> table app_team
However, app_contest does not have field teams in it. And app_team does not contain field contest.
Instead, there is a third table e.g. called app_contest_team that consists of three columns:
id
contest_id
team_id
And stores pairs of contest_id + team_id that represent connection between contests and teams.
So queryset = self.teams.all() is equal to the SQL expression:
SELECT * FROM app_teams WHERE id in
(SELECT team_id from app_contest_teams WHERE contest_id = '{self.id}');
Thus, you can see that you cannot anyhow manipulate ManyToManyField without having an instance initially saved in database. Because only after inserting into table app_contest it receives unique ID which can be later used for creating entries in the third table app_contest_teams.
And also because of that - save() is not required when you add new object to ManyToManyField because table of the model is not affected

How to append value to the ArrayField in Django with PostgreSQL?

I have created a model with the ArrayField in Django Model. I am using PostgreSQL for the database.
I want to create new rows in the database or update the existing rows.
But I can not insert or append data to the ArrayField.
Model
class AttendanceModel(BaseModel):
"""
Model to store employee's attendance data.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='attendance_user')
date = models.DateField()
time = ArrayField(models.TimeField(), blank=True, null=True)
class Meta:
unique_together = [['user', 'date']]
def __str__(self):
return f'{self.user.username} - {self.date}'
View
attend, created = models.AttendanceModel.objects.get_or_create(user=fingerprint.user, date=attendance_date)
attend.time = attend.time.append(attendance.timestamp.time())
attend.save()
The time field does not being created or updated with the new value.
How can I do this?
attend.time = attend.time.append(attendance.timestamp.time())
Here the return value of list.append(...) is being assigned to attend.time, which is None, thus the attend.time is not getting updated.
Try this instead,
attend, created = models.AttendanceModel.objects.get_or_create(
user=fingerprint.user, date=attendance_date
)
attend.time.append(attendance.timestamp.time())
attend.save()
Following this Answer I did this.
from django.db.models import Func, F, Value
.
.
.
attend, created = models.AttendanceModel.objects.get_or_create(user=fingerprint.user, date=attendance_date)
attend.time = Func(F('time'), Value(attendance.timestamp.time()), function='array_append')
attend.save()

Creating custom unique Id for new record but having duplicate error

creating an id and trying to insert a record with it, but having duplication error.
django.db.utils.IntegrityError: (1062, "1062 (23000): Duplicate entry
'715683160' for key 'projects_project.PRIMARY'", '23000')
import random
def makeUniqueLongId():
return random.randint(100000000, 999999999)
class Project(models.Model):
id = models.BigIntegerField(unique=True, primary_key=True, default=makeUniqueLongId(), editable=False)
...
How can I prevent id duplication? Is unique=True not enough to return new id with makeUniqueLongId()
Your code does not create a unique ID, but just a random one. The more often you run makeUniqueLongId() the higher is the probability that you will eventually get a duplicate ID.
Django's unique=True will prevent that an entry will be saved if its value already exists and that's exactly why you're getting the error (see documentation here). Moreover, since you are setting primary_key = True, there is no need for unique, because that condition will be set automatically.
Try adding a custom save() method to your model:
def makeUniqueLongId():
return random.randint(100000000, 999999999)
class Project(models.Model):
id = models.BigIntegerField(primary_key=True, editable=False)
...
# while id is not yet set, create new random id. If random id not already exists, save as new id.
def save(self, *args, **kwargs):
while not self.id:
new_id = makeUniqueLongId()
if not type(self).objects.filter(id=new_id).exists():
self.id = new_id
super().save(*args, **kwargs)

Significant performance issue with Django Admin - foreign key labels

I’m experience significant performance issue with Django Admin.
I have a mapping model where I map primary keys of 2 other modes
In my FundManagerMappingAdmin I try to represent the foreign key of the 2 tables with a label from the foreign key models.
The underlying models are about 4000 lines
I’m experiencing slow performance when retrieving the list in admin and then also when editing and updating
Could someone please point out the inefficiencies in this code?
Is there a better way please?
Admin.py
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
list_display = ['get_champ_fund_manager_id', 'get_evestment_name', 'get_sharepoint_name', ]
def get_champ_fund_manager_id(self, obj):
return obj.fund_manager_id
get_champ_fund_manager_id.short_description = 'CHAMP Manager ID'
def get_evestment_name(self, obj):
return obj.evestment_fund_manager_id.manager_name
get_evestment_name.short_description = 'Evestment Manager Name'
def get_sharepoint_name(self, obj):
return obj.sharepoint_fund_manager_id.manager_name
get_sharepoint_name.short_description = 'Sharepoint Manager Name'
def get_form(self, request, obj=None, **kwargs):
form = super(ChampFundManagerMappingAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['sharepoint_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.final_publications_fund_manager_id, obj.manager_name)
form.base_fields['evestment_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.evestment_fundmanager_id_bk, obj.manager_name)
return form
Models.py
class FundManagerMapping(models.Model):
fund_manager_id = models.AutoField(db_column='FundManagerId', primary_key=True)
sharepoint_fund_manager_id = models.ForeignKey(SharePointFundManager, models.DO_NOTHING, db_column='SharePointFundManagerId')
evestment_fund_manager_id = models.ForeignKey(EvestmentFundManager, models.DO_NOTHING, db_column='eVestmentFundManagerId')
class EvestmentFundManager(models.Model):
evestment_fund_manager_id = models.AutoField(db_column='eVestmentFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
evestment_fund_manager_id_bk = models.CharField(db_column='eVestmentFundManagerId_BK', max_length=50)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
class SharePointFundManager(models.Model):
sharepoint_fund_manager_id = models.AutoField(db_column='SharePointFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
research_fund_manager_id = models.CharField(db_column='ResearchFundManagerId', max_length=50, blank=True, null=True)
final_publications_fund_manager_id = models.CharField(db_column='FinalPublicationsFundManagerId', max_length=50, blank=True, null=True)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
You are showing the name of related entities (because of get_evestment_name and get_sharepoint_name) without joining/prefetching them. That means for every row that you display and every name of the related entity it requires django to make a database query. You need to override get_queryset() of the ModelAdmin and use select_related to tell django to join those entities from the beginning so that it does not need any additional queries to get those names:
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'sharepoint_fund_manager_id',
'evestment_fund_manager_id',
)
Also you don't name ForeignKey fields something_id. It is just sharepoint_fund_manager because what you get when you call fund_manager.sharepoint_fund_manager_id is not an id but an instance of SharePointFundManager. It is weird to call sharepoint_fund_manager_id.name. An id does not have a name attribute. A fund manager has.
Additionally Django does automatically create a property sharepoint_fund_manager_id for you if you call the field sharepoint_fund_manager to access the plain foreign key.

'NoneType' object has no attribute 'to_bytes'

New on python and having a hard time with solving error codes.
I have a form which adds rows to a postgresql databse. the form has an autofield which is primary key inside my models.py. Adding rows as such works, and the uniqueid fields counts up like inteded (1,2,3,...)
models.py:
class forwards(models.Model):
uniqueid = models.AutoField(primary_key=True)
user = models.CharField(max_length = 150)
urlA = models.CharField(max_length = 254)
counterA = models.DecimalField( max_digits=19, decimal_places=0,default=Decimal('0'))
urlB = models.CharField(max_length = 254)
counterB = models.DecimalField( max_digits=19, decimal_places=0,default=Decimal('0'))
timestamp = models.DateTimeField('date created', auto_now_add=True)
shortcodeurl = models.CharField(max_length = 254)
forms.py:
class AddUrlForm(forms.ModelForm):
class Meta:
model = forwards
# fields = '__all__'
exclude = ["user", "counterA", "counterB", "shortcodeurl", "uniqueid"]
The goal is to use the primary key value (which should be an integer according to here), transform it into "bytes" and then do a bytes-to-base64 conversion to create a shortcode-url. I want to store this shortcode inside the table. I try to do this in the views.py
views.py
def forwardthis(request):
forwardform = AddUrlForm(request.POST or None)
if forwardform.is_valid():
forward = forwardform.save(commit=False)
forward.user = request.user.username
uniqueid_local = forward.uniqueid
print(uniqueid_local)
uniqueid_local_bytes = uniqueid_local.to_bytes(3, byteorder='big')
shortcodeurl_local = urlsafe_base64_encode(uniqueid_local_bytes)
forward.shortcodeurl = shortcodeurl_local
forward.save()
My Problem:
I don't succeed in creating this shortcode URL and am getting an "NoneType" error. I tried modifying the models.py into BigIntegerField and IntegerField, but that didn't work. Adding " default=0 " to uniqueid = models.AutoField(primary_key=True) generated no error the first time I submitted a form, but then, when submitting a second form, it gives an error null value in column "timestamp" violates not-null constraint
To me, it looks like the uniqueid is not recognised like an integer. How to fix this?
Thanks for the help!
AutoFields are set by the database itself, so don't get a value until after you save. But you have not saved at that point, because you passed commit=False to the form save; this creates an instance in memory but does not send it to the db yet.
If you want this to work, you will have to remove that commit=False and accept the (tiny) cost of saving to the db twice.

Categories

Resources