Django REST Framework - nested serializer validation? - django

So I have models like so:
class Leaderboard(models.Model):
pass
class Column(models.Model):
leaderboard = models.ForeignKey(Leaderboard, related_name="columns", on_delete=models.CASCADE)
related_columns = models.ManyToManyField(self)
index = models.PositiveIntegerField()
And serializers like so:
class ColumnSerializer(ModelSerializer):
related_columns = serializers.PrimaryKeyRelatedField(queryset=Column.objects.all(), many=True)
class Meta:
model = Column
fields = ('leaderboard', 'related_columns', 'index',)
class LeaderboardSerializer(ModelSerializer):
children = ColumnSerializer(many=True)
class Meta:
model = Leaderboard
fields = ('columns',)
So what I'd like to do is verify that any columns added to related_columns for ColumnSerializer already belong to its Leaderboard parent. I have tried many times to access the Leaderboard or a Leaderboard ID (like by manually specifying id in fields) during creation of the ColumnSerializer to verify, but LeaderboardSerializer` is not initialized before Column so I cannot verify the details.
Basically, I want to modify queryset=Column.objects.all() to be queryset=self.instance.leaderboard.columns.all()
However I don't have access to Leaderboard inside Column. For example, if I access self.parent.instance/initial inside ColumnSerializer it is None until inside Leaderboard.validate_columns(). One thing I've thought of is to just do the validation on the Leaderboard side, but I still think it should be "doable" to do this validation inside Column in case I ever want to edit those directly, without first going through a Leaderboard...

Here is how I solved this problem:
def validate_columns(self, columns):
if not columns:
raise serializers.ValidationError("Leaderboards require at least 1 column")
# Make sure all column indexes are unique
indexes = [column['index'] for column in columns]
if len(set(indexes)) != len(columns):
raise serializers.ValidationError("Columns must have unique indexes!")
# Make sure all column keys are unique
keys = [column["key"] for column in columns]
if len(set(keys)) != len(columns):
raise serializers.ValidationError("Columns must have unique keys!")
# Validate that column.computation_indexes points to valid columns
for column in columns:
if 'computation_indexes' in column and column['computation_indexes']:
for index in column['computation_indexes'].split(","):
try:
if int(index) not in indexes:
raise serializers.ValidationError(f"Column index {index} does not exist in available indexes {indexes}")
except ValueError:
raise serializers.ValidationError(f"Bad value for index, should be an integer but received: {index}.")
return columns
I make sure that the columns are unique (both in their keys and indexes) and that the columns they are referencing exist as well.

Related

Is it possible to link multiple models to one fiel in django?

Let's say I have these models:
class Material(models.Model):
name = models.CharField([...])
class Consumable(models.Model):
name = models.CharField([...])
restores = models.IntegerField([...])
class Weapon(models.Model):
name = models.CharField([...])
damage = models.IntegerField([...])
# And then I have an 'inventory', like this one:
class Inventory(models.Model):
user = models.ForeignKey([...]) # to which user you want to link the item
item = models.ForeignKey([...]]) # which item
quantity = models.IntegerField([...]) # how many of it
I want to be able to have all Material, Consumable, and Weapon models listed in the 'item' field, so when you want to add an item as an inline, you would see all 3 models' objects.
Something like
# instead of this
item = models.ForeignKey(Consumable) # which item
# want something like this
item = models.ForeignKey(Consumable and Material and Weapon) # which item
# this wouldn't work ofc...
Is there a way to collect all 3 of them and pass them to the 'item' field, without the need of restarting the server? (when making a "choices" list that queries from a model you must restart the server to see the newly added objects, I don't want that.)
I also want to stick to the built-in admin of Django since it provided everything I need for the past couple of months, but I am open to any ideas.
I could be wrong but I think you are making this more complex than it needs to be. Instead of doing separate classes for materials (type of material) and consumable (type of product), you can have that built in the last class as model field as category or bolean fields.
class Products(models.Model):
material_type =
consumable = boolean for yes no or you can do multiple choice field
Then for items you can query the number of items based on material_type or consumable model fields (see query filters for for more).
all_items = Products.model.all()
consumable_items = Products.model.filter(your filter logic goes here)
Hope this helps!

Django - one field to many columns in db table

I would like to create a custom field with choices that store data in n (number of choices) db columns. It's should looks like dummy vector.
Example:
Choose type of product
This product in db table
id
kind_hygine
kind_road_equipment
kind_relax
kind_food
1
0
0
1
0
It is a legacy db, so I can not change db tables layout.
How can I achieve it?
try this
model.py
YOUR_CHOICES = [
("Road equipment","Road equipment"),
("Relax","Relax"),...
#it goes like this create list inside it create tuples as many as you want
#inside every tuple create 2 string elements the first is one will be
#stored inside your DB the seconde is the one will be shown to the user
]
class YourModel(models.Model):
.... #other model fields
your_choices_field= models.CharField(choices=YOUR_CHOICES)
now in your form
class YourForm(forms.ModelForm):
your_choices_field=forms.ChoiceField(choices=YOUR_CHOICES)

Django filter table by foreign keys in related manager column

I have three models in my Django application:
class MainData(models.Model):
# bunch of fields
class Label(models.Model):
label = models.CharField(max_length=512, null=True, unique=True)
class MapData(models.Model):
labelMatch = models.ForeignKey(Label, on_delete=models.CASCADE)
mainMatch = models.ForeignKey(MainData, on_delete=models.CASCADE)
Through my application, I have the user enter a label in a search box. What I would like to do is return the MainData rows whos' MapData.label_match field is the entry in Label.
For example, say the user enters the string 'main32' into the search box. My current thought is to first find Label rows that match 'main32', then use the RelatedManager labelmatch_set to get all of the MapData rows that MapData.mainMatch points to. So if there are 10 MapData rows whose labelMatch points to a Label entry with label='main32', I then want to retrieve all 10 MainData rows that the foreign key mainMatch points to.
Hopefully I explained that ok. I have gotten to:
matching_label_rows = Label.objects.filter(input)
matching_main_data_rows = matching_label_rows.mainMatch_set.????
How can I retrieve the pointed to MainData rows from matching_label_rows.mainMatch_set? And can this operation be done as a one-liner?
Instead of finding the matching Labels first, you can filter MainData on the mapdata__labelMatch relationship:
matching_main_data_rows = MainData.objects.filter(mapdata__labelmatch__label__icontains=input)
Lookups that span relationships

exclude a query result in another query

Here i want to do is that ,i want to list all the person who didn't blocked me.Here in the table Blocked there is two columns name
who and whose . In whose column i store the id of the person whom i blocked and in the who column i store my id. Now i want to do that, when the blocked person click on
view-person button in my web page he cannot see profile of the person one who blocked him.
when i did this query blocked_list = Blocked.objects.filter(whose = user_id). Now i got the list of the persons who blocked me. Now i want to exclude all this person from this query total_profiles = persons.objects.all().exclude(blocked_list). How can i do this.
models.py
class persons(models.Model):
full_name = models.CharField(max_length=200)
class blocked(models.Model):
who = models.ForeignKey(persons)
whose = models.IntegerField(null=True)
views.py
def blocked(request):
blocked_list = Blocked.objects.filter(whose = user_id)
total_profiles = persons.objects.all().exclude(blocked_list)
return render_to_response('profiles/view_all.html', {'total_profiles':total_profiles,'}, context_instance=RequestContext(request),)
please correct the question if it is not correct.
You can try this:
total_profiles = persons.objects.all().exclude(id__in = blocked_list.values_list('id', flat=True))
It's untested, but adapted from this answer.
Some notes:
if persons has the default manager, you can omit all().
whose does not have an index, so it will become slow when your dataset gets big. You can use a ForeignKey field instead of an IntegerField
the common convention is to capitalize class names and to write model names in singular i.e. Person instead of persons

How do I set a model field to the current count?

Say I have some groups with some items. I would like items to have a unique index within a group:
class Item(models.Model):
group = models.ForeignKey(Group, null=True)
index = models.IntegerField(default=0)
class Meta:
unique_together=('group','index')
def save(self):
if self.pk is None and self.group_id is not None:
self.thread_index = Item.objects.filter(group_id=group_id).count()+1
return super(Item, self).save()
But this is problematic because the update is not atomic, i.e. another transaction may have added another row after I calculate thread_index and before I write to the database.
I understand that I can get it working with catching IntegrityError and retrying. But I wonder if there's a good way to populate it atomically as part of the insert command, with SQL something like:
insert into app_item (group, index)
select 25, count(*) from app_item where group_id=25
Since you're using django models, what does adding an index field give you over the primary key added to all models unless you specify otherwise? Unless you need items to have sequential indices, which would still be problematic upon deleting objects, the id field would certainly appear to satisfy the requirement as you outlined it.
If you have to have this field, then an option could be to take advantage of signals. Write a receiver for the object's post_save signal and it could conceivably handle it.
One possible solution could be if you have issues with signals or custom save approach, calculate it at runtime by making index a property field
class Item(models.Model):
group = models.ForeignKey(Group, null=True)
def _get_index(self):
"Returns the index of groups"
return Item.objects.filter(group_id=group_id).count()+1
index = property(_get_index)
Now use index as field of Item model.