I am writing tests for my Django apps. I am using Django serializers to test API response. I found out that it allows extra fields:
from rest_framework import serializers
class ProjectSerializer(serializers.Serializer):
name = serializers.CharField()
ProjectSerializer({'name': "Project A", "state": "active"}).is_valid()
# True
ProjectSerializer().to_internal_value(data={'name': "Project A", "state": "active"})
# OrderedDict([('name', 'Project A')])
What I want is to ensure there will be no other fields in a response. Let's say, I expect this:
data = {'name': "Project A", "state": "active"}
ProjectSerializer(data, extra='forbid').is_valid()
# False
Using marshmallow I can do it like that:
from marshmallow import Schema, INCLUDE
class UserSchema(Schema):
class Meta:
unknown = RAISE
RAISE (default): raise a ValidationError if there are any unknown
fields
With pydantic it will be like this:
from pydantic import BaseModel, ValidationError, Extra
class Model(BaseModel, extra=Extra.forbid):
a: str
'forbid' will cause validation to fail if extra attributes are
included
Is there any options I can use with Django to perform this?
I don't see the need to do it this way, nor does rest-framework behave the way you are describing.
to confirm you can check the validated_data param that is passed down to the create or update method of the serializer.
Only the explicitly declared fields are available in the validated_data attribute (which is used for further crud operations)
Related
I have created one django admin page which is associated with a dummy model which doesn't exists.
Here is the code. The statistic model doesn't exists in database.
class StatisticModel(models.Model):
class Meta:
verbose_name_plural = 'Statistic'
class StatisticModelAdmin(admin.ModelAdmin):
model = StatisticModel
def get_urls(self):
view_name = 'changelist'
return [
path('settings/', my_custom_view, name=view_name),
]
admin.site.register(StatisticModel, StatisticModelAdmin)
In my git workflow I have below command,
python backend/django/manage.py test ./backend/django
Which is failing with error,
django.db.utils.ProgrammingError: relation "monitor_statisticmodel" does not exist
Please advise how can I avoid or skip this error?
https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_urls
This allows you to create admin pages with custom views without requiring models.
Before my answer: why bother have this model if it's not going to be used? If it's only there for testing, write "abstract = True" in the Meta class. If you don't want to do that then...
Maybe setting abstract=True would work? I'm not sure it will since Django's ModelAdmin might raise an error. What I'd advise is Django doesn't is setting "IS_TESTING" in your settings file. Imo, you should have multiple settings file. Then your model can look like:
class StatisticModel(models.Model)
class Meta:
verbose_name_plural = "Statistic"
abstract = getattr(settings, "IS_TESTING", False)
Another example is via a monkey patch and assuming that model is in admin.py. So during your test case, you can run this before your tests begin:
from project import admin
admin.StatisticModel.Meta.abstract = True
I have model with a field validator
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
class MyModel(model.Model):
name = models.CharField()
size = models.IntegerField(validators=[MinValueValidator(1),MaxValueValidator(10)])
The validator is working well in the django admin panel ,while I try to enter the value more than 10, it's showing me the error message 'Ensure this value is less than or equal to 10' and does not allow to save.
But, when I try in the django shell, the validator is not working, it allows to save the record, I don't know why is the validator not throwing error message here.
>>>form app.models import MyModel
>>>MyModel.objects.create(name="Some Name", size=15)
<MyModel: Some Name>
Can you please suggest me if anything I missed or any mistake i did here. Kindly help me to solve this problem, it will be very greatfull for me, Thanks in advance.
Django validation is mostly application level validation and not validation at DB level. Also Model validation is not run automatically on save/create of the model. If you want to validate your values at certain time in your code then you need to do it manually.
For example:
from django.core.exceptions import ValidationError
form app.models import MyModel
instance = MyModel(name="Some Name", size=15)
try:
instance.full_clean()
except ValidationError:
# Do something when validation is not passing
else:
# Validation is ok we will save the instance
instance.save()
More info you can see at django's documentation https://docs.djangoproject.com/en/1.10/ref/models/instances/#validating-objects
In administration it works automatically because all model forms (ModelForm) will run model validation process alongside form validation.
If you need to validate data because it is coming from untrusted source (user input) you need to use ModelForms and save the model only when the form is valid.
The validator only works when you are using models in a ModelForm.
https://docs.djangoproject.com/en/dev/ref/validators/#how-validators-are-run
You can perform model validation by overidding clean() and full_clean() methods
Validators work only with the Forms and model forms. Can't be used with the model definition because it runs at the app side not the DB side.
You can add this to your model and call it in save().
def save(self, *args, **kwargs):
self.run_validators()
super().save(*args, **kwargs)
def run_validators(self) -> None:
for field_name, field_value in model_to_dict(self).items():
model_field = getattr(UserSearchHistory, field_name)
field = getattr(model_field, 'field', object())
validators = getattr(field, 'validators', list())
for validator_func in validators:
if field_value is not None:
validator_func(field_value)
From django documentation:
Note that validators will not be run automatically when you save a
model, but if you are using a ModelForm, it will run your validators
on any fields that are included in your form.
https://docs.djangoproject.com/en/3.1/ref/validators/#how-validators-are-run
I ran into the same issue.
So the validators only work when you are using Forms and Model Form to fill it.
However, by creating in shell, you probably wanted to test the validators before going live.
So here is the additional piece of code to help in validating the validators.
>>>form app.models import MyModel
>>>MyModel.size.field.run_validators(value=<undesirable value>)
You can not run validator in creating you must run validation in instance if not exception occurred you must save it
It is worth mentioning that model field validators
like validate_color in here:
bg_color = models.CharField(
max_length=50, default="f0f2f5", validators=[validate_color]
)
work with restf_ramework (drf) Serializer class either.
https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py
so validators run when you call is_valid on ModelForm (from django) or is_valid on Serializer (from rest_framework).
I want to add logging of admin changes in my django project. I've done some of that through LogEntry model:
from django.contrib.admin.models import LogEntry
class LogEntryAdmin(admin.ModelAdmin):
list_display = ('__str__', 'action_time', 'user', 'content_type', 'object_id', 'object_repr', 'action_flag', 'change_message')
list_filter = ('content_type',)
search_fields = ['user__username',]
date_hierarchy = 'action_time'
admin.site.register(LogEntry, LogEntryAdmin)
This is great, if I change some field of an object in my database, I can see log entry for that action. But in this log entry I can see only that "field was changed", and I also want to see initial and result value of this field. How can I achieve this functionality?
You can extend the LogEntry class and add custom fields and then use pres_save, post_save etc., to store the required entries to your custom model.
I don't understand this behaviour. Let's say I open a Django shell and type:
from django.contrib.auth.models import User
user = User.objects.create(username="toto", email="titi")
Why does Django let me create this user (with an invalid email) without raising an error?
I have the same "no verification behaviour" creating a user in a POST in my API created with tastypie.
The question is:
As Django does not seem to check this by itself, where am I supposed to perform this kind of verifications sothat I don't have to write them several times (since a user can be created in several ways like website, API, etc.)?
Thanks.
Django doesn't implicitly do any validation if you just call .create() or .save() - you need to explicitly use model validation, or save the object via a
ModelForm. Your example with model validation would look like this:
user = User(username="toto", email="titi")
try:
user.full_clean()
except ValidationError as e:
# handle the error...
pass
user.save()
Or using a ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
f = UserForm(dict(username="toto", email="titi"))
if f.is_valid():
user = f.save()
else:
# handle error, ...
pass
Both model validation and ModelForms invoke the model field's validators, so in the case of the User's email, no additional work is needed for validation. If you need to do custom validation, you can do this in the ModelForm class - it is common to have a forms.py file in the app as a central place for Forms and ModelForms.
The same goes for Tastypie - the default configuration assumes the data submitted is valid. You can override this with the FormValidation class, which uses a Django Form or ModelForm for its validation. A full example would look something like this:
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
validation = FormValidation(form_class=UserForm) # UserForm from above
# more options here....
I want to allow the admins of my site to filter users from a specific country on the Admin Site. So the natural thing to do would be something like this:
#admin.py
class UserAdmin(django.contrib.auth.admin.UserAdmin):
list_filter=('userprofile__country__name',)
#models.py
class UserProfile(models.Model)
...
country=models.ForeignKey('Country')
class Country(models.Model)
...
name=models.CharField(max_length=32)
But, because of the way Users and their UserProfiles are handled in django this leads to the following error:
'UserAdmin.list_filter[0]' refers to field 'userprofile__country__name' that is missing from model 'User'
How do I get around this limitation?
What you are looking for is custom admin FilterSpecs. The bad news is, the support for those might not supposed to ship soon (you can track the discussion here).
However, at the price of a dirty hack, you can workaround the limitation. Some highlights on how FilterSpecs are built before diving in the code :
When building the list of FilterSpec to display on the page, Django uses the list of fields you provided in list_filter
Those fields needs to be real fields on the model, not reverse relationship, nor custom properties.
Django maintains a list of FilterSpec classes, each associated with a test function.
For each fields in list_filter, Django will use the first FilterSpec class for which the test function returns True for the field.
Ok, now with this in mind, have a look at the following code. It is adapted from a django snippet. The organization of the code is left to your discretion, just keep in mind this should be imported by the admin app.
from myapp.models import UserProfile, Country
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
class ProfileCountryFilterSpec(ChoicesFilterSpec):
def __init__(self, f, request, params, model, model_admin):
ChoicesFilterSpec.__init__(self, f, request, params, model, model_admin)
# The lookup string that will be added to the queryset
# by this filter
self.lookup_kwarg = 'userprofile__country__name'
# get the current filter value from GET (we will use it to know
# which filter item is selected)
self.lookup_val = request.GET.get(self.lookup_kwarg)
# Prepare the list of unique, country name, ordered alphabetically
country_qs = Country.objects.distinct().order_by('name')
self.lookup_choices = country_qs.values_list('name', flat=True)
def choices(self, cl):
# Generator that returns all the possible item in the filter
# including an 'All' item.
yield { 'selected': self.lookup_val is None,
'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
'display': _('All') }
for val in self.lookup_choices:
yield { 'selected' : smart_unicode(val) == self.lookup_val,
'query_string': cl.get_query_string({self.lookup_kwarg: val}),
'display': val }
def title(self):
# return the title displayed above your filter
return _('user\'s country')
# Here, we insert the new FilterSpec at the first position, to be sure
# it gets picked up before any other
FilterSpec.filter_specs.insert(0,
# If the field has a `profilecountry_filter` attribute set to True
# the this FilterSpec will be used
(lambda f: getattr(f, 'profilecountry_filter', False), ProfileCountryFilterSpec)
)
# Now, how to use this filter in UserAdmin,
# We have to use one of the field of User model and
# add a profilecountry_filter attribute to it.
# This field will then activate the country filter if we
# place it in `list_filter`, but we won't be able to use
# it in its own filter anymore.
User._meta.get_field('email').profilecountry_filter = True
class MyUserAdmin(UserAdmin):
list_filter = ('email',) + UserAdmin.list_filter
# register the new UserAdmin
from django.contrib.admin import site
site.unregister(User)
site.register(User, MyUserAdmin)
It's clearly not a panacea but it will do the job, waiting for a better solution to come up.(for example, one that will subclass ChangeList and override get_filters).
Django 1.3 fixed it. You're now allowed to span relations in list_filter
https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter