How to get object_set in serializer from Many to Many? - python

I want to allow saving some specific field only if it responds to some statement. Let me explain.
models.py
class classe(models.Model):
name = models.CharField(max_length=191)
groups = models.ManyToManyField('Group')
persons = models.ManyToManyField('Person')
class Group(models.Model):
name = models.CharField(max_length=191)
persons = models.ManyToManyField('Person')
class Person:
name = models.CharField(max_length=191)
serializers.py
class GroupSerializer(serializers.ModelSerializer):
persons = PersonSerializer(many=True, read_only=True)
person_id = serializers.PrimaryKeyRekatedField(queryset=Person.objects.all(), write_only=True)
def update(self, instance, validated_data, **kwargs):
instance.name = validated_data.get('name', instance.name)
person_field = validated_data.get('person_id)
classe = instance.classe_set #This one doesn't work
person_classe = classe.persons.all() #This one too
if (person_field in person_classe):
instance.persons.add(person_field)
instance.save()
return instance
So, the person_field can be saved only if the person is in the actual classe.
Everything work fine, but the two commented lines don't, so can't access the if statement.
Does someone know how can I figure it out ?

Because Group and Classe are ManyToManyfield with each other; therefore a Group can have many Classe and a Classe can have many groups.
Therefore to get the list of classes of a group:
classes = list(mygroup.classe_set.all())
In your update function, I'm guessing you're trying to add the person to the group if the person is in the classes associated with that group. If yes, here is what you need:
# first get all the associated Classe, you need the '.all()'
# keep in mind that .all() gives you a queryset
classes = instance.classe_set.all()
for c in classes:
students = c.persons.all() # persons of a classe of this group
if students.filter(id=person_field.id).exists():
# if this person is in the persons of this classe of this group
# then we add this person to this group
instance.persons.add(person_field)
break # we only need to add it once, right?
Hope this helps!

Related

Pass array of links in mutation [graphene/python/graphql]

i'm a little bit confuses about using graphene.
I am using the example of mutations on https://www.howtographql.com/graphql-python/3-mutations/ , but here only the example is shown how to create ONE link. Now it is more realistic for me that you have a list of links or other objects that you pass to your backend and later database. Is there anyone who has already implemented such an example?
I have taken a different example from https://docs.graphene-python.org/en/latest/types/mutations/#inputfields-and-inputobjecttypes . Below code snippet should help you in creating multiple instances in a single mutation.
import graphene
from .models import Person
class PersonInput(graphene.InputObjectType):
name = graphene.String(required=True)
age = graphene.Int(required=True)
class PersonType(DjangoObjectType):
class Meta:
model = Person
class CreatePerson(graphene.Mutation):
class Arguments:
person_objects = graphene.List(PersonInput, required=True)
persons = graphene.List(PersonType)
def mutate(root, info, person_objects):
persons = list()
for person_data in person_objects:
person = Person.objects.create(
name=person_data.name,
age=person_data.age
)
persons.append(person)
return CreatePerson(persons=persons)
mutation:
createPerson(personObjects: [{name: "Danish Wani" age:28}, {name: "Wani Danish" age:29}]){
persons{
name
age
}
}

Check if object has relation with other in ManyToMany field and aggregate field

I have a couple models as follows:
class Student(models.Model):
name = models.CharField()
classes = models.ManyToManyField(Class, related_name="students")
class Class(models.Model):
nombre = models.CharField
What I need is a way such that, when querying students in certain class, instead of just returnig the students enrolled in that class, it returns a QuerySet of all the students, each one with an aggregated field indicating if such student is enrolled in that class, e.g.:
[{'name':'student A', 'enrolled_in_physics':True}, {'name':'student B', 'enrolled_in_physics':False}]
I think it can be achieved through F() expressions along with ExpressionWrapper, but have no idea of how implement them; additionaly, the documentation and the examples are not very noob-friendly. Any help is appreciated, thanks!.
EDIT: Ok, I think using the word "list" is not the correct one, I need a normal QuerySet, such that let me do something like this:
student_a = query[0]
student_a.name
>>>'A'
student_a.enrolled_in_physics
>>>True
UPDATED
You could try defining a method within the student class:
class Student(models.Model):
name = models.CharField()
classes = models.ManyToManyField(Class, related_name="students")
def enrolled_in_class(self,class_name):
if len(Class.objects.filter(nombre=class_name,students__in=self)) > 0:
return True
else:
return False
students = Student.objects.all()
student_a = students[0]
student_a.name
student_a.enrolled_in_class('physics')
Original Answer:
Edit: Sorry misread question, I'll try and get an answer together for what you actually wanted: A list of all students, and binary true false if they are enrolled in a specific class.
Alright, I'm reading this as a:
I need a query that returns a list of dictionaries, indicating the student, and enrollment in a class.
#Assume: class_name = 'physics'
def get_class_list(class_name):
filtered_class = Class.object.get(nombre=class_name)
student_names = Students.objects.filter(classes__contains=filtered_class).values_list('name',flat=True)
class_list = []
for name in student_names:
class_list = {}
class_list['name'] = name
enrolled_class_string = 'enrolled_in_' + class_name
class_list[enrolled_class_string] = True
return class_list
This will give you a list of dictionaries with the keys named 'name', and 'class__name'

How can I use multiple models to point to the same collection?

I have a single collection that can represent multiple types of data:
class Taxes(db.Document):
meta = {'collection': 'taxes'}
type = db.StringField() # State, local, federal
owner = db.ReferenceField(User, unique=True)
name = db.StringField()
fiscal_year = db.IntField()
What I am wanting to do is have either a DynamicEmbeddedDocument or make this a DynamicDocument to hold different models.
For example:
class Taxes(db.Document):
...
# This is made up syntax
data = db.EmbeddedDocumentField(StateTaxes, LocalTaxes, FederalTaxes)
Or:
class Taxes(db.DynamicDocument):
...
class StateTaxes(Taxes):
state_name = db.StringField()
class LocalTaxes(Taxes):
locality_name = db.StringField()
The goal is to do this:
# Embedded Dynamic Document example
taxes = Taxes.objects(owner=current_user).all()
state_taxes = [tax.data for tax in taxes if tax.type == 'state']
state_names = [tax_data.state_name for tax_data in state_taxes]
# Dynamic Document example
taxes = Taxes.objects(owner=current_user).all()
state_taxes = [tax for tax in taxes if tax.type == 'state']
state_names = [tax.state_name for tax in state_taxes]
Notes:
I must be able to perform 1 query to get back all types**.
Models should be separate in order to allow for clean definitions.
This example is very small, there would be a growing number of Models with very different definitions**.
All Models will have 4 or 5 fields that are the same.
The dynamic data should be relatively easy to query.
**These are the main reasons I am not using separate collections
Is this possible?
You could make a base class that covers all the base attributes (fields) and methods that you need. For example:
class BaseTaxes(db.Document):
name = db.StringField()
value = db.IntegerField()
meta = {'allow_inheritance': True}
def apply_tax(self, value):
return value*(1+self.value)
With this base class you can then create different versions:
class StateTaxes(BaseTaxes):
state = db.StringField()
As such the StateTaxes class inherits both attributes of BaseTaxes and its methods (more details here). Because it inherits the BaseTaxes class, it will be saved in the same collection (BaseTaxes) and queries can reach all subclasses:
results = BaseTaxes.objects().all()
And then, to split results by subclass:
state_taxes = [item for item in results if isinstance(item,StateTaxes)]

django: exclude certain form elements based on a condition

I have some form fields that I want to include/exclude based on whether or not a certain condition is met. I know how to include and exclude form elements, but I am having difficulty doing it when I want it elements to show based on the outcome of a function.
Here is my form:
class ProfileForm(ModelForm):
# this_team = get Team instance from team.id passed in
# how?
def draft_unlocked(self):
teams = Team.objects.order_by('total_points')
count = 0
for team in teams:
if team.pk == this_team.pk:
break
count += 1
now = datetime.datetime.now().weekday()
if now >= count:
# show driver_one, driver_two, driver_three
else:
# do not show driver_one, driver_two, driver_three
class Meta:
model = Team
What I am trying to accomplish is, based on the standings of total points, a team should not be able to change their driver until their specified day. As in, the last team in the standings can add/drop a driver on Monday, second to last team can add/drop on Tuesday, and so on...
So the first problem -- how do I get the Team instance inside the form itself from the id that was passed in. And, how do I include/exclude based on the result of draft_unlocked().
Or perhaps there is a better way to do all of this?
Thanks a lot everyone.
This is actually fairly straightforward (conditional field settings) - here's a quick example:
from django.forms import Modelform
from django.forms.widgets import HiddenInput
class SomeForm(ModelForm):
def __init__(self, *args, **kwargs):
# call constructor to set up the fields. If you don't do this
# first you can't modify fields.
super(SomeForm, self).__init__(*args, **kwargs)
try:
# make somefunc return something True
# if you can change the driver.
# might make sense in a model?
can_change_driver = self.instance.somefunc()
except AttributeError:
# unbound form, what do you want to do here?
can_change_driver = True # for example?
# if the driver can't be changed, use a input=hidden
# input field.
if not can_change_driver:
self.fields["Drivers"].widget = HiddenInput()
class Meta:
model = SomeModel
So, key points from this:
self.instance represents the bound object, if the form is bound. I believe it is passed in as a named argument, therefore in kwargs, which the parent constructor uses to create self.instance.
You can modify the field properties after you've called the parent constructor.
widgets are how forms are displayed. HiddenInput basically means <input type="hidden" .../>.
There is one limitation; I can tamper with the input to change a value if I modify the submitted POST/GET data. If you don't want this to happen, something to consider is overriding the form's validation (clean()) method. Remember, everything in Django is just objects, which means you can actually modify class objects and add data to them at random (it won't be persisted though). So in your __init__ you could:
self.instance.olddrivers = instance.drivers.all()
Then in your clean method for said form:
def clean(self):
# validate parent. Do this first because this method
# will transform field values into model field values.
# i.e. instance will reflect the form changes.
super(SomeForm, self).clean()
# can we modify drivers?
can_change_driver = self.instance.somefunc()
# either we can change the driver, or if not, we require
# that the two lists are, when sorted, equal (to allow for
# potential non equal ordering of identical elements).
# Wrapped code here for niceness
if (can_change_driver or
(sorted(self.instance.drivers.all()) ==
sorted(self.instance.olddrivers))):
return True
else:
raise ValidationError() # customise this to your liking.
You can do what you need by adding your own init where you can pass in the id when you instantiate the form class:
class ProfileForm(ModelForm):
def __init__(self, team_id, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
this_team = Team.objects.get(pk=team_id)
teams = Team.objects.order_by('total_points')
count = 0
for team in teams:
if team.pk == this_team.pk:
break
count += 1
now = datetime.datetime.now().weekday()
if now >= count:
# show driver_one, driver_two, driver_three
else:
# do not show driver_one, driver_two, driver_three
class Meta:
model = Team
#views.py
def my_view(request, team_id):
profile_form = ProfileForm(team_id, request.POST or None)
#more code here
Hope that helps you out.

How does django one-to-one relationships map the name to the child object?

Apart from one example in the docs, I can't find any documentation on how exactly django chooses the name with which one can access the child object from the parent object. In their example, they do the following:
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.place.name
# Create a couple of Places.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
# A Restaurant can access its place.
>>> r.place
<Place: Demon Dogs the place>
# A Place can access its restaurant, if available.
>>> p1.restaurant
So in their example, they simply call p1.restaurant without explicitly defining that name. Django assumes the name starts with lowercase. What happens if the object name has more than one word, like FancyRestaurant?
Side note: I'm trying to extend the User object in this way. Might that be the problem?
If you define a custom related_name then it will use that, otherwise it will lowercase the entire model name (in your example .fancyrestaurant). See the else block in django.db.models.related code:
def get_accessor_name(self):
# This method encapsulates the logic that decides what name to give an
# accessor descriptor that retrieves related many-to-one or
# many-to-many objects. It uses the lower-cased object_name + "_set",
# but this can be overridden with the "related_name" option.
if self.field.rel.multiple:
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
return None
return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
else:
return self.field.rel.related_name or (self.opts.object_name.lower())
And here's how the OneToOneField calls it:
class OneToOneField(ForeignKey):
... snip ...
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(),
SingleRelatedObjectDescriptor(related))
The opts.object_name (referenced in the django.db.models.related.get_accessor_name) defaults to cls.__name__.
As for
Side note: I'm trying to extend the
User object in this way. Might that be
the problem?
No it won't, the User model is just a regular django model. Just watch out for related_name collisions.

Categories

Resources