How to implement reject/approve functionality with Django - django

For example, a model called Request, this model has a status field, status is by default "Awaiting", I want to make it possible for someone to change that through a form to "Approved" or "Rejected" I tried using some methods but they all didn't work.

req_db = Request.objects.get(pk=1) # get pk from form or somewhere
req_db.status = 'Approved' # or use models.TextChoices
req_db.save()

Related

How to test CheckboxSelectMultiple in save

I have a form (ModelForm) in Django, where I am adding a field for users in the init method as so:
self.fields["users"] = forms.ModelMultipleChoiceField(
queryset=users, widget=forms.CheckboxSelectMultiple, required=False,label="Add Designer(s)"
)
In the save method how I can iterate over the queryset for this field, however, I do not know how I can test if the particular model has been selected/checked. Help, please.
EDIT:
Let's say that you have a form where you want to be able to add users to a certain project, I set the users field as above (also usedMultipleChoiceField) but my real question is how do you determine the state of those checkboxes (which users should be added)?
Managed to fix it using MultipleChoiceField instead of ModelMultipleChoiceField. Then populated the choices with existing event IDs and passed it to the template.
In forms:
choices = forms.MultipleChoiceField(widget = forms.CheckboxSelectMultiple())
In views:
form.fields['choices'].choices = [(x.eventID, "Event ID: " + x.eventID) for x in unapproved]
Had to change some of the logic for finding and editing Event objects too.
The Django documentation states that a ModelMultipleChoiceField normalizes to a QuerySet of model instances. That means in your example, it will only return the users that have been checked. If none have been checked, it will return an empty QuerySet.
If you are overriding your ModelForm save method, you could include something like this:
selected_users = self.cleaned_data.get('users')
for user in selected_users:
project_users.add(user)

Django url manipulation - detail page with pk / id should not show based on attributes of that object - Best Practice

I have a django page that displays a list of links. Each link points to the detail page of the respective object. The link contains the pk/id of that object (something like ../5/detailObject/). The list is generated on the backend and has some filtering baked into it, e.g. only generate a link if that object has state x, etc.
Clicking on the links works, but users can still manipulate the url and pass a valid link with an incorrect state (a wrong pk/id is being handled with the get or 404 shortcut).
What is the best practice for handling this kind of scenario with django? Should that kind of filtering be placed in the object's model class instead of using function-based views as I do now?
Function based view:
If you want to restrict a set of objects to a particular user (for instance a user's orders), then you would need to set up the Order model to foreign key to the User model and then look up the order by both id and user:
views.py:
def get_order(request, id=0)
if request.method == 'GET':
try:
order = Order.objects.get(user=request.user, pk=id)
except Order.DoesNotExist:
return redirect(...)
And set up a url to handle:
url(r'^order/(?P<id>\d+)/$', views.get_order, name='get_order_by_id'),
As far as adding a slug field on the model after the fact, set up a second url:
url(r'^order/(?P<slug>[\w-]+)/$', views.get_order, name='get_order_by_slug')
And change the above view logic to first do a lookup by pk if pk is greater than 0 and then redirect back to the function using the slug from the looked up order (this assumes all looked-up records have slugs):
def get_order(request, slug='', id=0)
if request.method == 'GET':
try:
if id > 0:
order = Order.objects.get(user=request.user, pk=id)
return redirect(reverse('get_order_by_slug'), permanent=True, slug=order.slug)
order = Order.objects.get(user=request.user, slug=slug)
except Order.DoesNotExist:
return redirect(...)
You should also put unique=True on the slug field and ensure that the user is authenticated by placing the #login_required decorator on your view.
To restrict orders by a particular status, you could:
Create a set of statuses for your Order model, and then you could:
Pass a value for a kwarg in the view when you filter, or
Create a custom manager on the Order model
There are several ways you could create your statuses:
as a set of choices on the Order model
use the SmartChoices library
as a database field
If you create choices on the Order model, it could be something like this:
class Order(models.model):
STATUSES = (
('PLCD', 'Placed'),
('INTR', 'In Transit'),
('DLVR', 'Delivered')
)
status = models.CharField(max_length=4, default='', choices=STATUSES)
An acquaintance who is a very seasoned Django professional told me about the SmartChoices library. I have not used it yet but would like to try it at some point. The database field option would be my least preferred way of doing this because that seems to me like moving programming variables into the database; however, it would work.

limit choices in drop downs in django rest browsable api

Is there a way to limit what fields are populated (such as in dropdown selectors or list selectors) in the DRF browsable API?
Below is an image example of how DRF is suggesting choices of "projects" to the user that he should select. However, the logged in user may or may not have access to these projects, so I'd like to get control over what shows up here! It seems that the default behavior is to show all related objects.
It would be really useful if there was a way to link the objects populated in these fields to be set according to a get_queryset() function.
This page seems to hint that it might be possible, I just can't find an example of how to do it: http://www.django-rest-framework.org/api-guide/filtering/
You can define a new field based on some of the serializers.RelatedField-ish classes, and use the request in the context to redefine what the method get_queryset returns.
An example that might work for you:
class AuthorRelatedField(serializers.HyperlinkedRelatedField):
def get_queryset(self):
if 'request' not in self.context:
return Author.objects.none()
request = self.context['request']
return Author.objects.filter(user__pk=request.user.pk)
class BookSerializer(serializers.HyperlinkedModelSerializer):
author = AuthorRelatedField(view_name='author-detail')
Check the templates in DRF/temapltes/rest_framework/inline/select.html. The select object in DRF uses the iter_options method of the field to limit the options in the select tag, which in turn uses the queryset from get_queryset.
This would efectively limit the options of the select tag used in the browsable API. You could filter as well in the get_choices method if you want to preserve the queryset (check the code in DRF/relations.py). I am unsure if this impacts the Admin.
I don't fully understand what do you want but try queryset filter's __in function if you need to show only exact values:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
to_show = [ "user1", "user2", "user3"]
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username__in=username)
return queryset
you can add your values to to_show list and if queryset element equals one of them then it will be shown.
Also if you want to show only some fields of model you need to edit your Serializer's fields parameter:
class PurchaseList(serializers.ModelSerializer):
class Meta:
model = Purchase
fields = ('id', 'field1', 'field2', ...)

How to serialize data not coming from the request and properly validate it (ModelSerializer in Django Rest Framework)?

Using Django Rest Framework 3, Function Based Views, and the ModelSerializer (more specifically the HyperlinkedModelSerializer).
When a user submits a form from the client, I have a view that takes the request data, uses it to call to an external API, then uses the data from the external API to populate data for a model serializer.
I believe I have this part working properly, and from what I read, you are supposed to use context and validate()
In my model serializer, I have so far just this one overidden function:
from django.core.validators import URLValidator
def validate(self, data):
if 'foo_url' in self.context:
data['foo_url'] = self.context['foo_url']
URLValidator(data['foo_url'])
if 'bar_url' in self.context:
data['bar_url'] = self.context['bar_url']
URLValidator(data['bar_url'])
return super(SomeSerializer, self).validate(data)
Just in case, the relevant view code is like so:
context = {'request': request}
...
context['foo_url'] = foo_url
context['bar_url'] = bar_url
s = SomeSerializer(data=request.data, context=context)
if s.is_valid():
s.save(user=request.user)
return Response(s.data, status=status.HTTP_201_CREATED)
Now assuming I have the right idea going (my model does populate its foo_url and bar_url fields from the corresponding context data), where I get confused is how the validation is not working. If I give it bad data, the model serializer does not reject it.
I assumed that in validate(), by adding the context data to the data, the data would be checked for validity when is_valid() was called. Maybe not the case, especially when I print out s (after using the serializer but before calling is_valid()) there is no indication that the request object's data has been populated with the context data from validate() (I don't know if it should be).
So I tried calling the URLValidators directly in the validate() method, but still doesn't seem to be working. No errors despite giving it invalid data like 'asdf' or an empty python dict ({}). My test assertions show that the field indeed contains invalid data like '{}'.
What would be the proper way to do this?
You're not calling the validator.
By doing URLValidator(data['bar_url']) you're actually building an url validator with custom schemes (see the docs) and that's it. The proper code should be:
URLValidator()(data['bar_url'])
Where you build a default url validator and then validate the value.
But anyway I would not use this approach, what I would do instead is directly add the extra data (not using the context) and let DRF do the validation by declaring the right fields:
# Somewhere in your view
request.data['bar_url'] = 'some_url'
# In serializer:
class MySerializer(serializers.ModelSerializer):
bar_url = serializers.URLField()
class Meta:
fields = ('bar_url', ...)
To answer your comment
I also don't understand how this also manages to make it past the
Django's model validation
See this answer:
Why doesn't django's model.save() call full_clean()?
By default Django does not automatically call the .full_clean method so you can save a model instance with invalid values (unless the constraints are on the database level).

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.