I'm on my second Django project. On my first project I used all generic views, with only the most basic forms tied directly to a custom user model using UpdateView.
In this project I'm trying to implement user profile functionality. My custom user model has some extra dummy fields just so I can manipulate the data. This I refer so as the CustomUser model. I also have a UserAddress model containing addresses since a user can have more than one address. I have tried looking for other questions, and I get similar questions, but there is always something missing:
Django class based views - UpdateView with two model forms - one submit
Using Multiple ModelForms with Class-Based Views
Multiple Models in a single django ModelForm?
Django: multiple models in one template using forms
I've spent the last day or two looking for the "Django way" of doing what I want to do and have had no luck. My initial thought was that I could use a single template, fed with two ModelForms wrapped in a single <form> tag. The view would then process each form and update the CustomUser model and create or update the UserAddress models. I have figured out how to mash together the functionality using the base View CBV, but I suspect I'm duplicating a lot of functionality that I could probably find already done in Django. This is my view, where I handle the form instantiating manually, and feed the context.
class UserDetailsView(View):
def get(self, request):
user = request.user
user_basic = CustomUser.objects.get(pk=user.pk)
basic_form = UserBasicForm(instance=user_basic)
user_address = UserAddress.objects.get(user=user.pk)
billing_address_form = UserAddressForm(instance = user_address)
context = {'basic_form':basic_form,'billing_address_form':billing_address_form}
return render(request, 'mainapp/profile.html', context)
My post is the same at this point, as I haven't done the actual validation and saving yet.
class UserBasicForm(forms.ModelForm):
class Meta(forms.ModelForm):
model = CustomUser
fields = (
'username',
'first_name',
'last_name',
)
labels = {
'username':'Username',
'first_name':'First Name',
'last_name':'Last Name',
}
class UserAddressForm(forms.ModelForm):
class Meta(forms.ModelForm):
model = UserAddress
fields = (
'description',
'addressee',
'company',
'address_1',
'address_2',
'city',
'prov_state',
'post_zip',
'country',
)
labels = {
'description':'Address Description',
'addressee':'Addressee',
'company':'Company Name',
'address_1':'Address',
'address_2':'Address 2',
'city':'City',
'prov_state':'Province or State',
'post_zip':'Postal or Zip Code',
'country':'Country',
}
class CustomUser(AbstractUser):
objects = CustomUserManager()
def __str__(self):
return self.email
class UserAddress(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
description = models.CharField(max_length = 256, default='Description')
addressee = models.CharField(max_length = 256,)
company = models.CharField(max_length = 256, default='Self')
address_1 = models.CharField(max_length = 256,)
address_2 = models.CharField(max_length = 256,)
city = models.CharField(max_length = 256,)
prov_state = models.CharField(max_length = 256,)
post_zip = models.CharField(max_length = 256,)
country = models.CharField(max_length = 256,)
def __str__(self):
return self.description
Please go easy on me, I'll take most advice offered.
Edit
After reviewing some other SO questions and Django form examples, it appears that the final answer probably isn't SO material. That said, my observation is that for the Django built-in CBVs, the "best" base view is that which you can minimize or simplify the code you add. Using a TemplateView or FormView for my project in this case just depends on which methods I choose to re-write or override and for that, I'm still open to suggestions.
I'd do something like this (with betterforms):
class UserCreationMultiForm(MultiModelForm):
form_classes = {
'basic': UserBasicForm,
'address': UserAddressForm,
}
class UserDetailsView(View):
template = "mainapp/profile.html"
form_class = UserCreationMultiForm
success_url = reverse_lazy('home')
def form_valid(self, form):
# Save the user first, because the profile needs a user before it
# can be saved.
user = form['basic'].save()
address = form['address'].save(commit=False)
address.user = user
address.save()
return redirect(self.get_success_url())
Then rename your forms in your template to form.basic and form.address
Related
I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples
These are my models:
class Partner(models.Model):
name = models.CharField(max_length=200, verbose_name="Organisation name")
class ResearchActivity(models.Model):
title = models.CharField(max_length=200)
partner = models.ManyToManyField(ActivityPartner, blank=True)
I'd like, in the Django administration forms, to have a field in my Partner edit form representing the ResearchActivity linked to that Partner.
Can this be achieved by adding a field to my Partner model (say, naming it linked_partner) and then edit my admin.py like so:
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
search_fields = ['academic',]
autocomplete_fields = ['partnership_type', 'relationship_type', 'academic_links']
def get_changeform_initial_data(self, request):
return {'live_contract': ResearchActivity.objects.all().filter(linked_partner__id=request.ResearchActivity.partner.id)}
?
I have just come across in the display() decorator, new from Django 3.2. With it, I can simply do:
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
search_fields = ['academic',]
autocomplete_fields = ['partnership_type', 'relationship_type', 'academic_links',]
readonly_fields = ('get_ra',)
#admin.display(description='Live contract(s)')
def get_ra(self, obj):
return list(ResearchActivity.objects.filter(partner=obj.id))
to achieve what I want.
If I also wanted to edit those ManyToMany relations, I can use the inlines option:
class LiveContractsInline(admin.TabularInline):
model = ResearchActivity.partner.through
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
inlines = [
LiveContractsInline,
]
I have the model created for complaints. I tried a few methods to help a user create the complaint, but nothing works so I have given up and I just want to understand what to add in the views.py so that I can do that. The system basically has multiple users and I want the users to send in their complaints to the admin panel, so that they can be viewed, edited or deleted as per use on the dashboard. Right now my form does get created but it does not save the complaints in the admin panel.
models.py:
class Complaints(models.Model):
user = models.ForeignKey(User, on_delete= CASCADE, null = True, blank=True)
title = models.CharField(max_length=300)
description = models.TextField(null=True, blank= True)
highpriority = models.BooleanField(default=False)
document = models.FileField(upload_to='static/documents')
def __str__(self):
return self.title
what to add in the views.py? I've tried various things but I don't know what to do to make it work.
this if my views.py but I feel like the entire thing is wrong so I want an entirely new views:
class ComplaintCreate(CreateView):
model = Complaints
form = ComplaintForm
fields = '__all__'
success_url = reverse_lazy('New')
template_name = 'new.html'
I want the final page to look like this:
there is not form attribute in CreateView change form to form_class
class ComplaintCreate(CreateView):
model = Complaints
form_class = ComplaintForm
fields = '__all__'
success_url = reverse_lazy('New')
template_name = 'new.html'
I am pretty stuck working with DRF for the first time. I am looking to upload multiple Images to a single real estate Listing.
My image model
class Image(models.Model):
photo = models.ImageField(blank=True, upload_to=get_image_filename)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE)
my Image, Listing, and Listing detail serializers
class ListingSerializer(serializers.HyperlinkedModelSerializer):
image_set = ImageSerializerForListingDetail(many=True, required=False)
class Meta:
model = Listing
fields = ['url', 'address', 'image_set', ]
class ListingDetailSerializer(serializers.HyperlinkedModelSerializer):
user = AccountSerializer(read_only=True)
image_set = ImageSerializerForListingDetail(many=True, required=False)
class Meta:
model = Listing
fields = '__all__'
depth = 1
class ImageSerializerForListingDetail(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField()
class Meta:
model = Image
fields = ('image_url', )
def get_image_url(self, listing):
return listing.photo.url
My view
class ListingViewSet(viewsets.ModelViewSet):
queryset = Listing.objects.all()
serializer_class = ListingSerializer
detail_serializer_class = ListingDetailSerializer
permission_classes = [IsOwnerOrReadOnly, ]
'''Show detailed Listing view'''
def get_serializer_class(self):
if self.action == 'retrieve':
if hasattr(self, 'detail_serializer_class'):
return self.detail_serializer_class
return super(ListingViewSet, self).get_serializer_class()
I am having trouble figuring out how to upload/edit multiple Images, to a single Listing, and where to override. I would like it possible when both creating and editing listings. Any help is greatly appreciated. Thanks!
This specific use case does have a section dedicated in the docs for "Writable nested objects"
https://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
The doc should cover the appropriate example you are looking for!
It seems like this should do the trick, and then I still need to work on the update method.
class ListingSerializer(serializers.HyperlinkedModelSerializer):
user = UsernameSerializer(read_only=True)
image_set = ImageSerializerForListingDetail(many=True, required=False,
read_only=True)
class Meta:
model = Listing
exclude = ('url', )
depth = 1
def create(self, validated_data):
images_data = validated_data.pop('image_set')
listing = Listing.objects.create(**validated_data)
for image_data in images_data:
Image.objects.create(listing=listing, **image_data)
return listing
Is there anything special that needs to me done with Images, that my one big concern? I always thought I needed to request.FILES, but I am seeing that that has been depreciated in DRF 3?
I'm not sure to save my ManyToMany relationship. I found my exact problem in this thread: Django embedded ManyToMany form, except instead of Sales and Products models, I have models that make up a movie.
I tried the solution, but I receive a syntax error. I don't understand how Django should link the EquipmentModel, LightModel, and ActorModel to the ManyToMany relationship in MovieModel. So far (before trying the other thread's solution), the CharFields that are displayed on the form for LightModel, EquipmentModel, and ActorModel are not linked to the ManyToManyField in MovieModel. So when I save the forms and try to access a particular Movie's actors, all I see is a blank list. The solution from the other thread seems to make sense since it tries to link the models to the ManyToMany relationship in MovieModel, but I don't understand how Django knows which MovieModel to add to (how does it get the correct movieID?).
On a side note, is there a way to check for duplicate movies when the user presses the 'Submit' button on the form? I want to avoid creating duplicates.
views.py:
def add_movie(request, movieID=""):
if request.method == "POST":
form = MovieModelForm(request.POST)
eform = EquipmentModelForm(request.POST)
lform = LightModelForm(request.POST)
aform = ActorModelForm(request.POST)
print 'checking form'
print request.POST.items()
if form.is_valid() and eform.is_valid() and lform.is_valid() and aform.is_valid():
print 'form is valid'
movie_to_add = form.save()
e = eform.save()
l = lform.save()
a = aform.save()
movie_to_add.actors.add(a)
movie_to_add.lights.add(l)
movie_to_add.equipments.add(e)
# return HttpResponseRedirect('/data')
else:
# code for create forms ....
return render_to_response('add_movie.html', {'form':form, 'eform':eform,'lform':lform, 'aform':aform,}, context_instance=RequestContext(request))
Other code that may help:
forms.py
class LightModelForm(forms.ModelForm):
class Meta:
model = LightModel
class ActorModelForm(forms.ModelForm):
class Meta:
model = ActorModel
class EquipmentModelForm(forms.ModelForm):
class Meta:
model = EquipmentModel
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
fields = ("title", "rank")
models.py
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
class LightModel(models.Model):
light = models.CharField(max_length=20)
class MovieModel(models.Model):
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
def __str__(self):
return self.title
Edit: removed unnecessary init and fields thanks to DTing
Edit2: Fixed!
There is a whole lot of stuff going wrong here in addition to what spulec said.
Your models.py look okay.
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
class LightModel(models.Model):
light = models.CharField(max_length=20)
class MovieModel(models.Model):
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
def __str__(self):
return self.title
You don't need to override the __init__ method on forms if you are not changing anything on init. You also don't need to be explicit about the fields if you want to include them all.
class LightModelForm(forms.ModelForm):
class Meta:
model = LightModel
class ActorModelForm(forms.ModelForm):
class Meta:
model = ActorModel
class EquipmentModelForm(forms.ModelForm):
class Meta:
model = EquipmentModel
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
fields = ("title", "rank")
your view doesn't really make sense unless for every movie you are trying to add you also want to:
add a new movie to the db using the submitted post data
create one actor object and add to db
create one light object and add to db
create one equipment object and add to db
take those three objects and add them to another movie's m2m relationships.
This other movie is some movie that you pulled from the urlconf and passed to your view, not the one you just created.
This all seems a little strange.
what i think you want to do is create all the equipment, actors and lights objects so they are in your db already, and use the default m2m formfield widget to select them when adding a movie.
so:
forms.py
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
urls.py:
url(r'^add_movie/$', add_movie)
views.py:
def add_movie(request):
if request.method=='POST':
form = MovieModelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse('success')
else:
form = MovieModelForm()
context = {'form':form }
return render_to_response('some_template.html', context,context_instance=RequestContext(request))
you could combine adding actors, lights, and equipment into the same form but that's a bit much for me to write out right now.
As far as modifying your original code to add those lights, actors, and equipment to the movie you just created, you could do this:
if form.is_valid() and eform.is_valid() and lform.is_valid() and aform.is_valid():
new_movie = form.save()
e = eform.save()
l = lform.save()
a = aform.save()
new_movie.actors.add(a)
new_movie.lights.add(l)
new_movie.equipments.add(e)
Change it to:
movie_to_add = get_object_or_404(MovieModel, id=movieID)