Code architecture - Flask - Where to put form validation from database? - validation

I'm wondering where should I put a validation form which accessing database.
Basically I will need user to enter item_type and I want to check first i the item_type has exist in database.
There are 3 options:
In the database model, I have ItemType class and I put function add() which will check if existing item has exist or not
In view, so in the route of the page, from wtforms form.validate_on_submit(), I do a check to get data from database, and if exist I will put error in here
In wtforms validate(), adding extra validation after default validation of the Form class
I've seen people using number 2 and 3, but not sure which one is the best. The error message that I want will also need to display it on the specific field of the form (this is achieveable by method 2 and 3 since they have reference to the form field) but then again since it is related to accessing database, maybe it is better to put everything regarding database access to model functions?

In my opinion, if it comes from a form then it should be validated on that form and then raise an error for that specific field when invalid. See the example bellow:
class SigninForm(Form):
"""Form for signin"""
email = StringField('Email',
validators=[
DataRequired("Email shouldn't be empty."),
Email('Email format is not correct.')
])
password = PasswordField('Password',
validators=[DataRequired("Password shouldn't be empty.")])
def validate_email(self, field):
"""
verify if account exists and if not raise an error in
that field.
"""
user = User.query.filter(User.email == self.email.data).first()
if not user:
raise ValueError("Account doesn't exist.")
def validate_password(self, field):
"""
Verify if password is valid and if not raise an error in
that field.
"""
if self.email.data:
user = User.query.filter(User.email == self.email.data).first()
if not user or not user.check_password(self.password.data):
raise ValueError('Password is not correct.')
else:
self.user = user
The view function for this example:
#app.route('/signin', methods=['GET', 'POST'])
def signin():
"""Signin"""
form = SigninForm()
if form.validate_on_submit():
# sign in function to register user into a session
signin_user(form.user)
return redirect(url_for('site.index'))
return render_template('account/signin/signin.html', form=form)

Related

django rest framework: SlugRelatedField options- limit by user

Note: I'm new to django rest framework. First project with it. My model has a foreign key which in the serializer I link by a SlugRelatedField. This related model is a small list of options that can be selected however what is available for selection depends on the user (or more specifically the user group).
I have found this question that says how to get the user into a serilaizer. But this doesn't seem to help as the field definition is static right?
Removing the irrelevant parts I have:
class MyModelSerializer(serializers.ModelSerializer):
sequence = serializers.SlugRelatedField(
many=False,
read_only=False,
slug_field='prefix',
queryset=Sequence.objects.filter(active=True,
sequence_groups__sequence_group_id__in=SequenceGroup.objects.filter(users=serializers.CurrentUserDefault()))
)
This query works as I also use it in a normal form. When I start the dev. server I get an exception:
TypeError: int() argument must be a string, a bytes-like object or a number, not 'CurrentUserDefault'
So my question is how can I get the current user into the queryset of the SlugRelatedField?
It's funny how after hours of trying, writing down the question leads one to a working solution.
Add request to sterilizer context in ModelViewSet
Simply add below method to the ModelViewSet:
def get_serializer_context(self):
return {"request": self.request}
Adjust the queryset of the SlugRelatedField in the constructor of the Serializer
def __init__(self, *args, **kwargs):
super(MyModelSerializer, self).__init__(*args, **kwargs)
# superuser can choose from all sequences, normal users can only choose from
# active sequences he is assigned to
request = self.context.get("request")
if request and hasattr(request, "user"):
sequence = self.fields['sequence']
if request.user.is_superuser:
sequence.queryset = Sequence.objects.all()
else:
sequence.queryset = Sequence.objects.filter(active=True,
sequence_groups__sequence_group_id__in=SequenceGroup.objects.filter(users=request.user))
In my case the admin should be able to select any of the available options hence the extra condition.

Django REST Framework - access verbose_name of fields in ModelSerializer

Say I have the following Model:
class Book(Model):
title = CharField(verbose_name="Book title")
and a ModelSerializer:
class BookSerializer(ModelSerializer):
class Meta:
model = Book
fields = "__all__"
I would like to have a function get_verbose_names which returns verbose names of the fields in the model. This is what I have so far:
def get_verbose_names(serializer):
return [field.label for field in serializer.get_fields().values()]
It seems to work fine but problems occur when I use this for the builtin User model. The only fields which work are ID, E-mail, Active, Superuser status and Staff status. The special thing about those fields is that their verbose name differs from their name. Django REST Framework is probably hiding a super-smart logic which checks this and refuses to set the field label to its verbose name in such cases.
Do Django REST Framework's fields have the verbose names hidden somewhere, or they don't copy them from the original Django model fields at all and I am screwed? Or will the trick be to override this logic? I tried and could not find it.
Django REST Framework really has the mentioned "super-smart logic". It is the function needs_label in utils.field_mapping:
def needs_label(model_field, field_name):
"""
Returns `True` if the label based on the model's verbose name
is not equal to the default label it would have based on it's field name.
"""
default_label = field_name.replace('_', ' ').capitalize()
return capfirst(model_field.verbose_name) != default_label
Probably the easiest way to bypass this annoying feature is to do this:
def get_verbose_names(serializer):
return [field.label or name.replace("_", " ").capitalize()
for name, field in serializer.get_fields().items()]
Explained in words, check the field label and if none was auto-generated for it, use the needs_label logic to determine it.

Django Rest Framework "This field is required" only when POSTing JSON, not when POSTing form content

I'm getting a strange result whereby POSTing JSON to a DRF endpoint returns:
{"photos":["This field is required."],"tags":["This field is required."]}'
Whereas when POSTing form data DRF doesn't mind that the fields are empty.
My model is:
class Story(CommonInfo):
user = models.ForeignKey(User)
text = models.TextField(max_length=5000,blank=True)
feature = models.ForeignKey("Feature", blank=True, null=True)
tags = models.ManyToManyField("Tag")
My serializer is:
class StorySerializer(serializers.HyperlinkedModelSerializer):
user = serializers.CharField(read_only=True)
def get_fields(self, *args, **kwargs):
user = self.context['request'].user
fields = super(StorySerializer, self).get_fields(*args, **kwargs)
fields['feature'].queryset = fields['feature'].queryset.filter(user=user)
fields['photos'].child_relation.queryset = fields['photos'].child_relation.queryset.filter(user=user)
return fields
class Meta:
model = Story
fields = ('url', 'user', 'text', 'photos', 'feature', 'tags')
And my api.py is:
class StoryViewSet(viewsets.ModelViewSet):
serializer_class = StorySerializer
def get_queryset(self):
return self.request.user.story_set.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
The results:
# JSON request doesn't work
IN: requests.post("http://localhost:8001/api/stories/",
auth=("user", "password",),
data=json.dumps({'text': 'NEW ONE!'}),
headers={'Content-type': 'application/json'}
).content
OUT: '{"photos":["This field is required."],"tags":["This field is required."]}'
# Form data request does work
IN: requests.post("http://localhost:8001/api/stories/",
auth=("user", "password",),
data={'text': 'NEW ONE!'},
).content
OUT: '{"url":"http://localhost:8001/api/stories/277/","user":"user","text":"NEW ONE!","photos":[],"feature":null,"tags":[]}'
The issue here isn't obvious at first, but it has to do with a shortcoming in form-data and how partial data is handled.
Form data has two special cases that Django REST framework has to handle
There is no concept of "null" or "empty" data for some inputs, including checkboxes and other inputs that allow for multiple selections.
There is no input type that supports multiple values for a single field, checkboxes being the one exception.
Both of these combine together to make it difficult to handle accepting form data within Django REST framework, so it has to handle a few things differently from most parsers.
If a field is not passed in, it is assumed to be None or the default value for the field. This is because inputs with no values are not passed along in the form data, so their key is missing.
If a single value is passed in for a multiple-value field, it will be treated like the one selected value. This is because there is no difference between a single checkbox selected out of many and a single checkbox at all in form data. Both of them are passed in as a single key.
But the same doesn't apply to JSON. Because you are not passing an empty list in for the photos and tags keys, DRF does not know what to give it for a default value and does not pass it along to the serializer. Because of this, the serializer sees that there is nothing passed in and triggers the validation error because the required field was not provided.
So the solution is to always provide all keys when using JSON (not including PATCH requests, which can be partial), even if they contain no data.

how to check if user has permission to delete objects in django

I am trying to write a view method that responds to an AJAX request to delete an entry. I want to check if the end user is the actual author of the Entry before deleting that Entry. Does my "if" statement accomplish this?
VIEWS.PY
latest_entries=Entry.objects.order_by('-pub_date')[:16]
#login_required
def delete_object(request):
if request.is_ajax():
object_name = request.POST.get('entryname')
targetobject = Entry.objects.get(author=object_name)
if request.user = targetobject.author:
targetobject.delete()
return HttpResponseRedirect('/storefront/')
MODELS
Class Entry(models.Model):
author = models.CharField(max_length=30)
subject = models.CharField(max_length=30)
description = models.CharField(max_length=30)
You're almost there. request.user is an instance of django.utils.SimpleLazyObject, so you won't be able to do an == comparison of request.user to a CharField, of which the value is a string under the covers.
You need to do something like:
if request.user.username == targetobject.author:
targetobject.delete()
or just use whatever field from the User object is synonymous with Entry.author.
I'd say your model is wrong. author should be a ForeignKey to the auth.User model. Then your comparison would work (with the change to ==), and there are other benefits too in terms of grouping and querying by user attributes.

How to load initial data into ModelMultipleChoiceField - Django 1.6

I'm creating my first site using Django and having trouble loading initial data into a ModelForm linked to the built in Django group table.
Right now, users can go to a group page and select from an array of groups they would like to join. When they return to the page later, they see the same list of options/checkboxes again with no indication of which groups they already belong to.
I can't figure out how to have the intial group data load into the form, such that if you are already a member of "group 1" for example, that checkbox is already checked. I would also like to have it so that you could uncheck a box, and so when you submit the form you could be leaving some groups and joining others at the same time. Any help appreciated! My code below:
class GroupForm(ModelForm):
groupOptions = forms.ModelMultipleChoiceField(queryset=Group.objects.all(), label = "Choose your groups",
widget=forms.CheckboxSelectMultiple())
class Meta:
model = Group
fields = ['groupOptions']
def groupSelect(request):
if request.method == 'POST':
form = GroupForm (request.POST)
if form.is_valid():
group = form.cleaned_data['groupOptions']
request.user.groups = group
return render (request, 'groups/groupSelect.html' , {'form':form})
else:
form = GroupForm()
return render (request, 'groups/groupSelect.html' , {'form':form})
Took me a few days and some trial and error, but figured this one out on my own. Just needed to modify the second to last line in the code above. The ModelForm loads all available groups as options, and the line below causes the groups the user already belongs to to be checked.
form = GroupForm(initial={ 'groupOptions': request.user.groups.all() })

Resources