Django - force pk_url_kwarg to query other model instances - django

Consider the following code:
views.py
class BHA_UpdateView(UpdateView):
model = BHA_overall
pk_url_kwarg = 'pk_alt'
form_class = BHA_overall_Form
To my understanding, pk_url_kwarg = 'pk_alt' will query and return instances of model = BHA_overall.
Is there any way that I can force pk_url_kwarg to query
& return other model instances defined in models.py (like model = other_model), while having my get_object() method to return objects in model = BHA_overall? What CBV should I use (I think UpdateView is not a good choice in this case)?
++ I'm trying to make a page that allows users to manage information about the product they use. So, ultimately I will implement forms, and the user input needs to be saved in DB
++ I need pk_url_kwarg = 'pk_alt' to query other models and generate url. But I still need get_object() method to return objects in model = BHA_overall to generate form fields on the user side.

From my understanding you need a django form generated from BHA_overall, but the data should be saved to AnotherModel right?
I will propose 2 solutions to this problem, Choose what best fits you.
Multiple views:
Have multiple views for the task, What I mean is create a view which creates the form for the frontend using BHA_overall, you can create both Create and Update view this way and update view's initial could be overwritten so form will have expected value when editing. And now post the data to another view which handles the post data. This view can have your AnotherModel doing its thing.
Using Django Form:
If you dont like having multiple views, You can keep things simple by creating a form yourself. Create a DjangoForm with the same fields you want to show to the user and use it in to create your own views, Now you wont need BHA_overall and use your AnotherModel to save datal.

Related

Getting fields from extra manager methods using django-rest-framework

I have the following custom model manager in Django that is meant to count the number of related comments and add them to the objects query set:
class PublicationManager(models.Manager):
def with_counts(self):
return self.annotate(
count_comments=Coalesce(models.Count('comment'), 0)
)
Adding this manager to the model does not automatically add the extra field in DRF. In my API view, I found a way to retrieve the count_comments field by overriding the get function such as:
class PublicationDetails(generics.RetrieveUpdateAPIView):
queryset = Publication.objects.with_counts()
...
def get(self, request, pk):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset.get(id=pk))
data = {**serializer.data}
data['count_comments'] = queryset.get(id=pk).count_comments
return Response(data)
This works for a single instance, but when I try to apply this to a paginated list view using pagination_class, overriding the get method seems to remove pagination functionality (i.e. I get a list of results instead of the usual page object with previous, next, etc.). This leads me to believe I'm doing something wrong: should I be adding the custom manager's extra field to the serializer instead? I'm not sure how to proceed given that I'm using a model serializer. Should I be using a basic serializer?
Update
As it turns out, I was using the model manager all wrong. I didn't understand the idea of table-level functionality when what I really wanted was row-level functionality to count the number of comments related to a single instance. I am now using a custom get_paginated_response method with Comment.objects.filter(publication=publication).count().
Original answer
I ended up solving this problem by creating a custom pagination class and overriding the get_paginated_response method.
class PaginationPublication(pagination.PageNumberPagination):
def get_paginated_response(self, data):
for item in data:
publication = Publication.objects.with_counts().get(id=item['id'])
item['count_comments'] = publication.count_comments
return super().get_paginated_response(data)
Not sure it's the most efficient solution, but it works!

How do I get the results of a multiple choice field from Django?

I set up a model multiple choice field, where a user can select multiple choices. I want to be able to get the choices that a user selected when filling out the form. What can I call to get the results that the user picked? Also, how would I iterate over those results (what does it return)?
Here's the field:
CHOICES = MyMultipleModelChoiceField(queryset=MODEL.objects.all(), widget=forms.CheckboxSelectMultiple, required=False)
Thanks!
Your post is incomplete there is too little informations you can to the following.
Use CreateView to create a new instance and then grab your data from your db, it will look like :
class CategoryCreate(generic.CreateView):
form_class = your_form_class
model = your_model
def get_success_url(self):
return reverse_lazy('your_view')
And in your next view you will be able to grab the data from your model.

How to set a model field based on the current user in a Django CreateView [duplicate]

I have a model named Domain which looks like this:
class Domain(models.Model):
"""
Model for storing the company domains
"""
user = models.ForeignKey(
User
)
host = models.CharField(
null=False, verbose_name="Host", max_length=128, unique=True
)
I'd like to use Django's generic views for doing CRUD operations on this. There is one field in this model that needs user input but the foreign key field doesn't need any user input. How can I exclude that field from the form that my generic view generates but assign it the value of the current authenticated user.
Thanks.
Have a look at Russel's answer to a similar question on the django-users group earlier this week.
Quoting the answer*:
Forms and Views solve different problems.
The View is solving the problem of "how do I handle this request and
convert it into a response?". The Form is solving the problem of "How
do I convert the POST data in this request into a model object (or a
change to a model object)?".
Very roughly, a view is doing the following:
View gets a request
View works out whether this is a GET or a POST
If its a POST, View asks the Form to turn the Post into a model change
Form returns success or failure
View responds to the success or failure of the Form.
View returns a response.
The functionality of the Form is a complete subset of the
functionality of the View -- and for this reason, it's a completely
interchangable internal component.
Now, in simple situations, it's possible for a View to guess all the
defaults for the form -- all it needs to know is that you're dealing
with a Foo model, and it can construct a default Foo ModelForm.
However, if you have more sophisticated form requirements, you're
going to need a customized Form.
We could have implemented this by exposing all the options of
ModelForm on the View class; but in order to keep everything clean, we
kept the ModelForm isolated, and provided the View with a way to
specify which Form class it's going to use.
So - to cover your use case of excluding fields, you define a
ModelForm that excludes the fields, then let the CreateView know the
form you want to use:
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
exclude = ('user', 'name', 'content_inlined')
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
I'm guessing when you say "fix a values for a field", you mean setting
the values of user, name and content_inlined before you save the new
Campaign instance; to do this, you need to inject some extra code into
the form processing logic of the form:
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
def form_valid(self, form):
form.instance.user = ... (something meaningful.. e.g., self.request.user)
return super(CreateCampaignView, self).form_valid(form)
This overrides the default behavior when the form is valid, and sets
the extra values. The super() implementation of form_valid() will then
save the instance.
For the record, this could also be done by overriding the save()
method on the ModelForm -- however, if you do that, you lose the
request object, which you will need if you're trying to set the
instance values to something that is request-sensitive.
*the original answer set self.object.user instead of form.instance.user. This gives an AttributeError so I have changed it above.

Django forms and querysets

Lets say I need to filter the options available in a multiple select box.
in my view I have:
class ArticleCheckbox(forms.ModelForm):
article= forms.ModelMultipleChoiceField(queryset=Article.objects.all(),required=False, widget=forms.CheckboxSelectMultiple)
class Meta:
model = Book
fields = ('m2m_article',)
.
In my view I will assign:
articleform = ArticleCheckbox()
articleform.fields["m2m_article"].queryset = Article.objects.filter(category = "Animals")
How does the assigning of the queryset in the view affect the queryset from classes (Article.object.all()) ?
Does it overwrite? I do not think so.
I would like to override the queryset. How can I do it?
Does this work?
article=forms.ModelMultipleChoiceField(queryset=Article.objects.all().filter(category = "Animals"),required=False, widget=forms.CheckboxSelectMultiple)
Directly in the model. Or do you want to leave the filtering to the view to do different things?
The way you're doing it is correct, except for you assign the class and not an instance of ArticleCheckBox
articleform = ArticleCheckbox()
When the form is initialised it is given a default queryset and you are overriding it, the initial one will never query the database since no data is ever needed to be retrieved at that point.

Django restrict options of ManyToMany field in ModelForm based on model instance

I want to show only options already stored in models' ManyToManyField.
I have model Order which I want to have a Model based form like this:
class OrderForm(ModelForm):
class Meta:
model = Order
fields = ['amount', 'color']
Now I do not want to display all colors as choices, but instead only color instances saved in ManyToManyField of another model. The other model is Design:
class Design(models.Model):
color = models.ManyToManyField('maker.Color')
# ...
Is this at all possible while using ModelForm?
Attempt
I have tried doing it by having a ModelForm of Design and setting instance:
class ColorForm(ModelForm):
class Meta:
model = Design
fields = ['color']
And then in view:
color_form = ColorForm(instance=design)
But I don't exactly understand what setting instance does, and I think instance is not what I am looking for as it still lists all colors.
The instance setting has nothing to do with limiting the choices. In essence, it simply populates the form's values with the ones from a specific record. You usually provide an instance in an edit operation, whereas you skip it in an add operation.
The representation of a models.ManyToManyField in the ModelForm is a forms.ChoiceField for which you can simply override its queryset property, and specify the queryset you desire.
Therefore, in your view:
form = OrderForm()
form.fields['color'].queryset = Design.object.all() # for example