Auto Incrementing natural keys with django / postgres - python

Let me preface this in saying that I'm a UI dev who's trying to branch out into more backend coding, so excuse me if my verbiage is off at all. This is could be a duplicate, but i'm not sure what on god's good green earth i'm even supposed to call what i want to do.
Basically, I have categories, and images. I need to label each image with an acronym of the category it belongs to, and increment a sku after.
For Example, the following images would be automatically labeled like...
ABC-1
ABC-2
DEF-1
DEF-2
DEF-3
ABC-3*
*note: I want it to increment the ID based on the category, not the total # of images
How would I achieve this in idiomatic Django?
Models:
class Group(models.Model):
title = models.CharField(max_length=200)
abbv = models.CharField(max_length=200)
urlified = models.CharField(max_length=200)
description = models.TextField(blank=True)
hidden = models.BooleanField()
def __unicode__(self):
return self.title
class Photo(models.Model):
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
class Meta:
ordering = ('pub_date',)

If you want true composed primary keys, you might want to use django-compositepks, but that is not ideal. You might be better off breaking DRY and recording the number (see the category_auto_key field and default).
Transactions will solve it this way:
from django.db import transaction
class Group(models.model):
# your fields
img_count = models.IntegerField()
#transaction.atomic
def next_sku(self):
self.img_count += 1
self.save()
return self.img_count
class Photo(models.Model):
# your fields
category_auto_key = models.IntegerField(editable=False)
def category_image(self):
return self.group.abbv+"-"+str(self.category_auto_key)
def save(self, *args, **kwargs):
if not self.category_auto_key:
self.category_auto_key = self.group.next_sku()
super(Photo, self).save(*args, **kwargs)
When you need this in your templates, just enclose it in double brackets:
{{ photo.category_image }}

I'm curious if you just want to generate and store the acronym and sku in a text field, or if you are trying to create relationships between your image categories?
If the later, I would look for a different approach.
If the former, i would use a customized set or save method (hook?) for your image model. It will need do a small one time lookup to count the number of acronym already existing, but I wouldn't worry about the performance too much.
Wasn't sure how to do this exactly in Django off the top of my head, but it looks like the accepted answer works similarly. Anyways, here is my attempt at setting a Model Field during save. Be warned this in untested.
After looking into it more I think that Beltiras' solution is better
class Photo(models.Model):
# simple column definitions
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
# new column for storing abbv sku
category_label = models.CharField(max_length=200)
# save override
def save(self, *args, **kwargs):
# hopefully only set category_label on first save, not sure this
# works, open to other ideas
if (self.pk is None):
count = Photo.objects.filter(group=self.group).count()
label = self.group.abbv + '-' + count
setattr(self, 'category_label', label)
# call the super class' save method
super(Photo, self).save(*args, ** kwargs)
The part I am least sure about is:
count = Photo.objects.filter(group=self.group).count()
The idea is to query the photos table for photos in the same group and count them. This may need to be replaced with a direct SQL call or done some other way. Let me know what you find.

Related

Programatically setting a Model's Fields in Django

Say I have a model Food
class Food(models.Model):
description = models.TextField()
toppings = models.TextField()
scheme = models.ForeignKey(FoodScheme, models.CASCADE)
And I want to have another class, a FoodScheme which describes which of the fields must be set in a specific Food class.
class FoodScheme(models.Model):
scheme_name = models.TextField()
requires_description = models.BooleanField(default=False)
requires_toppings = models.BooleanField(default=False)
But instead of hard coding this, I want to programmatically set these fields up, so any change in Food will change the FoodScheme class too.
An example implementation (that doesn't work, for several reasons, but I think gets my point across):
class FoodScheme(models.Model):
scheme_name = models.TextField()
for f in Food.get_fields():
setattr(self, f"requires_{f.name}", models.BooleanField(default=False))
Any ideas would be greatly appreciated.
As suggested by iklinac, the easiest way to get this to work is using a JSONField. This allows the content to be dynamic.
Here's an example GenericScheme:
class GenericScheme(models.Model):
scheme_name = models.TextField(default="Unnamed Scheme")
scheme_json = models.JSONField(default=dict)
model = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.scheme_json = {}
self.update()
def update(self):
if self.model is None:
return
for f in self.model._meta.get_fields():
if f.name not in self.scheme_json:
self.scheme_json[f.name] = False

How to go about splitting up multiple select in a Django form?

To make a more user-friendly interface on my website I would like to cut up a multiple select in a couple of multiple selects based on an attribute. How would you go about this?
Models.py
I basically have 4 classes as visualized below. I have a schedule to which tasks can be related. Furthermore the user needs to be able to assign user fields per schedule. An example of a user field could be 'topic' or 'priority: whatever the user decides. Each user field can have multiple user field values. For example, values for 'priority' could be 'Urgent', 'Normal' and 'No rush'. Each task should then have a value assigned to it. Ideally I would like it only to have one possible value per user field. So following the examples above a task can not be 'urgent' and 'normal' at the same time. This is another thing I need to figure out, so any hints are welcome, but outside the scope of this question.
class Schedule(models.Model):
title = models.CharField(max_length=50)
def __str__(self):
return self.title
class Tasks(models.Model):
schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
title = models.TextField()
start = models.DateTimeField()
end = models.DateTimeField()
userfieldvalues = models.ManyToManyField("UserFieldValues", blank=True)
def __str__(self):
return self.title
class UserField(models.Model):
name = models.CharField(max_length=50)
schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
def __str__(self):
return self.name
class UserFieldValues(models.Model):
value = models.CharField(max_length=50)
userfield = models.ForeignKey(UserField, on_delete=models.CASCADE, related_name="values")
def __str__(self):
return self.value
Template and forms.py
I created a form:
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ("title", "start", "end", "userfieldvalues")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = 'Task Title'
self.fields['title'].widget.attrs['rows'] = 1
self.fields['start'].widget.format = '%H:%Mh, %d-%m-%Y'
self.fields['start'].input_formats = ['%H:%Mh, %d-%m-%Y']
self.fields['end'].widget.format = '%H:%Mh, %d-%m-%Y'
self.fields['end'].input_formats = ['%H:%Mh, %d-%m-%Y']
In my template I render the form. For the user field values it now shows all the options is a multiple select. I would like to create a multiple select per UserField, but I don't know how to go about.

Insert multiple keywords in one field

My question is how i can insert multiple keywords in one django field and show them in a template like stackoverflow tags.
Models:
class Jobs(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(blank=True, default='')
company = models.ForeignKey(Company, on_delete=models.CASCADE)
tags = ?????
Create another class and use many-to-many relationship between jobs class (tags) and new class:
class Tags(models.Model):
tag_name=models.CharField()
In jobs class
tags=models.ManyToManyField(Tags)
For show in template you can use for loop, etc.
Make it a Comma separated value.
class Jobs(models.Model):
tags = models.TextField()
def tag_list(self):
return self.tags.split(",")
def add_tag(self, tag_str):
current_tags = self.tag_list()
current_tags.append(tag_str)
current_tags = set(current_tags)
new_tag_string = ",".join(current_tags)
self.tags = new_tag_string
# you could save the model now or let caller save it outside of this method. I suggest letting caller save the model.
def remove_tag(self, tag_str):
current_tags = self.tag_list()
current_tags.remove(tag_str)
new_tag_string = ",".join(current_tags)
self.tags = new_tag_string
# you could save the model now or let caller save it outside of this method. I suggest letting caller save the model.

A for loop in forms.py fails to load data immediately after it's sent | Python Django

In the forms.py I have a short piece of code which loads the data, but only after I edit print("hello") in it.
The code as follows:
models.py:
class CreateAssignment(models.Model):
user = models.ForeignKey(User, editable=False, blank=True, null=True)
progress = models.CharField(max_length=254, editable=False, blank=True, null=True)
class SetAssignment(models.Model):
mechanic = models.ForeignKey(User, editable=False, blank=True, null=True)
assignment = models.IntegerField(blank=True, null=True)
The mechanic is a permission, plus this mechanic's ID will show in the url of the website, when you will try to set an assignment for this mechanic.
forms.py:
class SetAssignmentForm(forms.ModelForm):
ASSIGNMENT_CHOICES = ()
for item in CreateAssignment.objects.all():
if item.progress == 'Scheduling':
user = User.objects.get(id=item.user_id).username
ASSIGNMENT_CHOICES += (
(item.id, user + ' - ' + str(item.id)),
)
assignment = forms.ChoiceField(choices=ASSIGNMENT_CHOICES, help_text='This is the assignment that you want to apply to this mechanic.')
class Meta:
model = SetAssignment
fields = ('assignment', )
The user_id in this situation is the user that has been set in the CreateAssignment model.
Now the issue is:
The for loop in the SetAssignmentForm works, but it loads data after I put a print in it or when I remove the print from it. Which of course shouldn't really affect the code.
Is there something I'm overlooking? I've been programming in Python Django for 8 weeks now, so if this is a basic program failure, please refer me to a page, because I haven't been able to find any information on this issue.
Thanks for the help.
For the ones that want to know:
views.py:
#login_required
def set_assignment(request):
form = SetAssignmentForm()
id = request.GET.get('id')
user_results = User.objects.filter(pk=id).values()
return render(request, 'pages/set_assignment.html', {'form': form, 'user_results': user_results})
Gif so you can visually see what's happening:
https://drive.google.com/open?id=1u7gfdiS7KitQWNVuvQEEOFJ9wD3q9rY6
You must not write code like this at class level. Anything at that level is only executed once, at definition time - ie when the class is first imported.
If you need to make the values dynamic, you should put the logic inside the __init__ method:
class SetAssignmentForm(forms.ModelForm):
assignment = forms.ChoiceField(choices=[], help_text='This is the assignment that you want to apply to this mechanic.')
def __init__(self, *args, **kwargs):
super(SetAssignmentForm, self).__init__(*args, **kwargs)
items = CreateAssignment.objects.filter(progress='Scheduling').select_related('user')
choices = [(item.id, '{} - {}'.format(item.id, item.user.username)) for item in items]
self.fields['assignment'].choices = choices
(Note, your query logic was very inefficient; my code only hits the database one time.)
However, here you don't even need to do that, because Django already has a form field - ModelChoiceField - that takes its values from the database. You can use a custom subclass of that to show the representation:
class AssignmentField(forms.ModelChoiceField):
def label_from_instance(self, item):
return (item.id, '{} - {}'.format(item.id, item.user.username))
class SetAssignmentForm(forms.ModelForm):
assignment = forms.AssignmentField(queryset=CreateAssignment.objects.filter(progress='Scheduling').select_related('user'))

Recursive Relationship with a Description in Django Models

My project involves sorting many images. As part of this sorting, I want to be able to manually (as the user) mark several images as duplicates of each other with a brief description of why each relationship was created. These relationships will not be defined at the time an image is loaded into Django, but at a later time after uploading all the images.
My question: How can I create an unlimited number of duplicates? Aka, how would I define that several images are all related to each other, and include a CharField description of why each relationship exists?
This is a django app and the code is from models.py.
Thank you.
from django.db import models
class tag(models.Model):
tag = models.CharField(max_length=60)
x = models.IntegerField(null=True)
y = models.IntegerField(null=True)
point = [x,y]
def __unicode__(self):
return self.tag
#...
class image(models.Model):
image = models.ImageField(upload_to='directory/')
title = models.CharField(max_length=60, blank=True, help_text="Descriptive image title")
tags = models.ManyToManyField(tag, blank=True, help_text="Searchable Keywords")
#...
##### HELP NEEDED HERE ##################
duplicates = [models.ManyToManyField('self', null=True), models.CharField(max_length=60)]
##########################################
def __unicode__(self):
return self.image.name
You'd have to go with an extra model for grouping those duplicates, because you want a description field with it. Something like
class DupeSeries(Model):
description = CharField(...)
members = ManyToManyField("image", related_name="dupes", ...)
Example usage:
img = image(title="foo!", image="/path/to/image.jpg")
dup_of_img = image(title="foo!dup", image="/path/to/dup/image.jpg")
img.save()
dup_of_img.save()
dupes_of_foo = DupeSeries(description="foo! lookalikes")
dupes_of_foo.members.add(img, dup_of_img)
# Notice how *img.dupes.all()* returns both image instances.
assert(list(img.dupes.all()) == [img, dup_of_img])

Categories

Resources