Save related models in one query - django

Hi I have two related models, I need to save it depends on json data from Angular.
Here is models:
Class Company(models.Model):
name = models.CharField(
max_length=255, )
class ContactPerson(models.Model):
company = models.ForeignKey(
Company,
related_name='contact_persons', )
name = models.CharField(
max_length=255, )
Here is part of view:
class CompanyCreate(JsonView):
#JsonView write by me, contains some methods
def post(self, *args, **kwargs):
data = json.loads(self.request.body)
company = CompanyForm(data=data['company'])
contacts = ContactForm(data=data['contacts'])
if company.is_valid():
company.save()
if contacts.is_valid():
contacts.save(commit=False)
contacts.company = company
contacts.save()
return HttpResponse()
Company is saving, but I cant valid contacts form, because I cant get company.id from first form.

I don't think you could save them at once because:
They are instances that has foreign key relationships, so one wouldn't exist before the other.
They are 3 different model instances so it doesn't make sense to combine them in one form.
If you just want to save the ones that are valid and reject the ones that are invalid, what you currently do is enough with one change, capturing the return value of save() function:
class CompanyCreate(JsonView):
#JsonView write by me, contains some methods
def post(self, *args, **kwargs):
data = json.loads(self.request.body)
company_form = CompanyForm(data=data['company'])
contacts_form = ContactForm(data=data['contacts'])
if company_form.is_valid():
company = company_form.save()
if contacts.is_valid():
contacts = contacts_form.save(commit=False)
contacts.company = company
contacts.save()
return HttpResponse()
If you want "all or none" logic, meaning you either save everything if everything is valid, otherwise don't save anything, you should exclude the foreign key on each ModelForm, so that when you valid the forms, they ignore the validation of existence of ForeignKey but not all other fields. See if that makes sense.

Related

how to create a SimpleListFilter in django

I don't succeed to write a query filter.
I have 3 models: Patient, Prescription and User
I write you only what is relevant for my question
Patient:
class Patient(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
Prescription:
class Prescription(models.Model):
user = models.ForeignKey(
User,
null=True,
blank=False,
on_delete=models.DO_NOTHING
)
file_extention = models.CharField(
'file extention',
max_length=8,
null=True,
blank=True,
)
So the relation between both of models (Patient and Prescription) are through User.
in the PatientAdmin, I want to filter on the file_extension according pdf or jpg of the prescription uploaded.
I created a SimpleListFilter but impossible to find the right query.
class PrescriptionFileExtensionFilter(SimpleListFilter):
"""This filter is being used in django admin panel in
patient model."""
title = 'Prescription File Ext'
parameter_name = 'file_extention'
def lookups(self, request, model_admin):
return (
('pdf', 'PDF'),
('jpg', 'JPG'),
)
def queryset(self, request, queryset):
for user in queryset:
if self.value() == 'pdf':
return queryset.filter(user=user.user).filter
(prescription__file_extention="pdf")
if self.value() == 'jpg':
return queryset.filter(user=user.user).filter
(prescription__file_extention="jpg")
That's not working...
Do I need the for user in queryset:
need What could be the query to bring me all the users with a prescription with file_extension = "pdf" (or "jpg")
You are trying to get a key from the prescription object in print(mydict['file_extention']) which I believe is causing the issue - you would instead access that property via mydict.file_extention - though I should add that mydict is not an accurate variable name for a model object as it isn't a dictionary. I don't think that for loop is actually doing anything other than printing a particular value so it can be removed altogether.
As an aside, you have two filters on your queryset, this can just be expressed as a single filter, separate by a comma, e.g.
return queryset.filter(user=user.user, prescription__file_extention="pdf")
You are also calling user.user, presumably you just want to get the user model which is kept in request.user - is that what your for loop was trying to do?
Edit
If you want to get all of the users but just filtered by JPG or PDF then you need to remove two things:
The for-loop of for user in queryset
The filter of .filter(user=user.user)
The for loop is unnecessary in the queryset function and the filter is just getting a single user, but you want to get all of them - correct?

How can I validate uniqueness of an instance in a collection in django rest framework

I have the following models.
class ServerGroup(models.Model):
name = models.SlugField(unique=True)
factor = models.IntegerField()
class ServerGroupMember(models.Model):
class Meta:
unique_together = (
("server_group", "position"),
("server_group", "server"),
)
position = models.IntegerField()
server_group = models.ForeignKey(
"ServerGroup", related_name="servers", on_delete=models.CASCADE
)
server = models.ForeignKey("Server", on_delete=models.CASCADE)
A ServerGroup has a couple of properties, name and factor, and a collection of ServerGroupMember objects. Each ServerGroupMember object, contains an integer position and a reference to a Server object. For a given ServerGroup the position must be unique, and for a given ServerGroup the server must be unique. However, globally, the position and server objects do not have to be unique, as in 2 ServerGroups may contain a server at postion 1, and the same Server may appear in multiple Server Groups, just not multiple times in the same Server Group.
Given that I have the following serializers, how can I validate the above? The model currently does validate the condition at the database level, but will raise a unique constraint error if I attempt to violate it. What I want is to be able to detect this in my views, such that I can return an appropriate validation error message response before it has a chance to hit the DB and raise that exception.
class ServerGroupMemberSerializer(serializers.ModelSerializer):
class Meta:
model = models.ServerGroupMember
fields = ("position", "server")
server = serializers.SlugRelatedField(
slug_name="name", queryset=models.Server.objects.all()
)
class SrvereGroupSerializer(serializers.ModelSerializer):
class Meta:
model = models.ServerrGroup
fields = ("name", "factor", "servers")
servers = ServerGroupMemberSerializer(many=True, required=False)
def create(self, validated_data): ...
def update(self, validated_data): ...
You can alwas check your validations when overriding the clean() method in your model, or also validating in serializers.py.
Also, the validation only concerns your not ForeignKey fields, since you can't add a ServerGroupMember member_one twice to a ServerGroup server_test.
class ServerGroupMember(models.Model):
class Meta:
unique_together = (
("server_group", "position"),
("server_group", "server"),
)
position = models.IntegerField()
server_group = models.ForeignKey(
"ServerGroup", related_name="servers", on_delete=models.CASCADE
)
server = models.ForeignKey("Server", on_delete=models.CASCADE)
def clean(self):
super().clean()
if self.position in ServerGroup.server_group_set.all().values_list('position', flat=True):
raise ValidationError(f"Position {self.position} already exists for ServerGroup {self.server_group.name}")
I figured out one way to do this, but still curious if this is the best approach here. This seems to work fine for creating new ServerGroupMember instances. Although not sure if it will work as well for the Update case, that is yet to be tried.
In the views, while instantiating the serializer, I passed to the constructor of the serializer, a context object containing the ServerGroup name. E.g.
def post(self, request: Request, name: str, format=None) -> Response:
server_group = self.get_object() # defined elsewhere
serializer = serializers.ServerGroupMemberSerializer(
data=request.data, context={"server_group": server_group.name}
)
...
Then in the ServerGroupMemberSerializer I added field-level validators, e.g.
def validate_position(self, value):
server_group = self.context.get("server_group")
try:
models.ServerGroupMember.objects.get(
server_group__name=server_group, position=position
)
except models.ServerGroupMember.DoesNotExist:
return value
raise serializers.ValidationError(
f"A server group member with position {position} already exists"
)
def validate_server(self, value):
... # Follow same pattern as above

How can I create model instance via Serializer without creating models from nested serialziers?

I have model with many links into it:
class Travel(BaseAbstractModel):
tags = models.ManyToManyField(
Tag,
related_name='travels',
)
owner = models.ForeignKey(
'users.TravelUser',
related_name='travel_owner'
)
payment = models.ForeignKey(
Payment,
related_name='travels',
)
country = models.ForeignKey(
Country,
related_name='travels,
)
........
Many of these models have only two fields with unique name and image.
I create serializer for each of these models and put them in TravelSerializer
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
Based on docs I override create() and update.
The problem is, when I sent JSON data, Django create each model from nested serializers. But I want to create only Travel instance. Also I want receive and respond serialized object not only pk field.
UPDATE
I solved this problem, put code in the answer. Now I can receive and respond with Serializer data without creating object.
But I think the DRF provides more elegant approach then I do. It is my first project with DRF, maybe I miss something and there's an easier solution.
I decide override to_internal_value() put it in custom serailizer and inherit all nested serializers from it:
class NestedRelatedSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
try:
pk = data['pk']
except (TypeError, KeyError):
# parse pk from request JSON
raise serializers.ValidationError({'_error': 'object must provide pk!'})
return pk
Get all pk from it and save in create and updated methods:
def update(self, instance, validated_data):
# If don't get instance from db, m2m field won't update immediately
# I don't understand why
instance = Travel.objects.get(pk=instance.pk)
instance.payment_id = validated_data.get('payment', instance.payment_id)
instance.country_id = validated_data.get('country', instance.country_id)
# update m2m links
instance.tags.clear()
instance.tags.add(*validated_data.get('tags'))
instance.save()
return instance
I'm not exactly sure I understand what you want to do, but could setting read_only_fields is the Meta class be what you need ?
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
class Meta:
read_only_fields = ('tags',)
See this section in the docs.

django admin add data with fixed value in some field

class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.

Django admin handling one to many relation

I have a django model as following
class Project(models.Model)
name=models.CharField(max_length=200)
class Application(models.Model)
proj=models.ForeignKey(Project, null=True, blank=True)
I need to modify the admin form of the project to be able to assign multiple applications to the project, so in the admin.py I have created a ModelAdmin class for the project as following
class ProjectAdmin(ModelAdmin)
form=projectForm
project_apps=[]
and the project form as following
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
project_apps =forms.ModelMultipleChoiceField(queryset=Application.objects.all(),required=False,)
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
if self.instance.id is not None:
selected_items = [ values[0] for values in Application.objects.filter(project=self.instance) ]
self.fields['project_apps'].initial = selected_items
def save(self,commit=True):
super(ProjectForm,self).save(commit)
return self.instance
by doing this I have a multiple select in the create/edit project form.
what I need is to override the save method to save a reference for the project in the selected applications?
how can I get the selected applications ????
Not entirely sure what you're trying to do, but maybe this?
def save(self,commit=True):
kwargs.pop('commit') # We're overriding this with commit = False
super(ProjectForm,self).save(commit)
if self.instance:
for a in self.cleaned_data['project_apps']:
a.proj = self.instance
a.save()
return self.instance
Now, I can't remember if in this case, self.cleaned_data['project_apps'] will actually contain a list of Application objects or not. I suspect it will, but if not this function will take care of that:
def clean_project_apps(self):
app_list = self.cleaned_data['project_apps']
result = []
for a in app_list:
try:
result.append(Application.objects.get(pk=a)
except Application.DoesNotExist:
raise forms.ValidationError("Invalid application record") # to be safe
return result
All in all I think this form is a bad idea though, because basically what is happening here is you're displaying all of the application records which doesn't make sense, since most of them will be associated with other projects.
Oh oh oh!!! Just noticed you wanted this to show up in a Multiple Select list!
You're (probably) doing it wrong
A multiple select means this isn't a one-to-many relationship. It's a many-to-many relationship.
This is what you want to do, easy peasy, doesn't require any custom forms or anything.
class Project(models.Model)
name=models.CharField(max_length=200)
project_apps = models.ManyToMany('Application', null=True, blank=True)
class Application(models.Model)
# nothing here (NO foreign key, you want more than one App/Proj and vice versa)
Indicating that this is a many-to-many field in Project will automagically create the multiple select box in admin. Ta da!