Django CreateView - ignores defaults on POST-test - python

I have a simple create view for a simple model with default-value fields. I want to test this setup by supplying only fields which don't have a default value.
The test fails, because no object was created in the database. Having played around with prints, I know the following:
The models clean() passes, and is supplied with the default values.
The response claims "This field is required." for maxVar and minVotes.
(And sending both values lets the tests pass.)
The failing test is:
from django.test import TestCase
from django.utils import timezone
from django.core.urlresolvers import reverse
import datetime
from testcase.models import Poll
class PollCreateTest(TestCase):
def test_create_with_description_only(self):
"""description should be sufficient to create a poll."""
self.assertEqual(Poll.objects.count(), 0)
x = self.client.post(reverse('rating:create'), {'description':'A Poll'})
#print x
self.assertEqual(Poll.objects.count(), 1)
With corresponding models.py:
from django.db import models
from django.core.urlresolvers import reverse
class Poll(models.Model):
description = models.TextField()
pub_date = models.DateTimeField('date published')
minVotes = models.IntegerField(default=5)
maxVar = models.FloatField(default = 0.0)
finished = models.BooleanField(default=False)
And views.py:
from django.shortcuts import render
from django.views import generic
from .models import Poll
class CreateView(generic.edit.CreateView):
model=Poll
fields=['description', 'maxVar', 'minVotes']
I am trying to get myself familiar with Django. The problem can probably be solved by writing a FormView with custom clean() methods. But I would like to understand WHY this doesn't work and, ideally, how to solve the problem.
I use Django 1.8 with Python 2.7.8.

The initial value is only used for two purposes: to render the initial value in the html form input, and to see if the submitted value has changed. It is not used to validate the form: not passing the actual data to the form is the same as explicitly changing the initial data to an empty value.
As your maxVar and minVotes fields are required (they don't have blank=True), deleting the initial values and passing no data to the form is not allowed. You have to pass both of them if you want the form to validate.

Related

How to customize the admin form for a custom image model in Wagtail CMS?

I need to add a string based unique ID to Wagtail’s image model. These IDs are a relatively short combination of letters, numbers and punctuation, e.g. "AS.M-1.001". So I am using a custom image model with Django’s standard CharField for that with the argument unique=True. But the editing form unfortunately does not check if the ID is unique. So I can use the same ID for multiple images. This check for uniqueness does work in any other standard form in Wagtail, e.g. the Page model. But not for the image model.
from django.db import models
from wagtail.images.models import Image, AbstractImage
class CustomImage(AbstractImage):
custom_id = models.CharField(max_length=32, unique=True, null=True, blank=True)
admin_form_fields = ( 'custom_id', ) + Image.admin_form_fields
My approach would be to override the editing form with a custom one to display more warnings and errors, like you can do with base_form_class for Wagtail’s Page model etc., as documented here. I tried both wagtail.admin.forms.WagtailAdminModelForm as well as wagtail.images.forms.BaseImageForm.
from wagtail.images.forms import BaseImageForm
from wagtail.admin.forms import WagtailAdminModelForm
class CustomImageForm(WagtailAdminModelForm):
# add more logic here
pass
class CustomImage(ArtButlerIDMixin, AbstractImage):
...
base_form_class = CustomImageForm
Both lead to the same exception:
raise AppRegistryNotReady("Models aren't loaded yet.")
So a tried to resort the apps in my settings with no effect. Does the standard approach how to override an admin form in Wagtail work for the image model at all? What could be other ways to get a unique string identifier working here? ... or to customize this form.
Solution (Update)
Following #gasman’s advice, I added the following line to my settings/base.py:
WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
WAGTAILIMAGES_IMAGE_FORM_BASE = 'images.forms.CustomImageForm' # NEW
And added a the following form to a forms.py in my images app:
from django.core.exceptions import ValidationError
from wagtail.images.forms import BaseImageForm
from .models import CustomImage
class CustomImageForm(BaseImageForm):
def clean(self):
cleaned_data = super().clean()
custom_id = cleaned_data.get("custom_id")
if CustomImage.objects.filter(custom_id=custom_id).exists():
raise ValidationError(
"Custom ID already exists"
)
return cleaned_data
Images in Wagtail don't use WagtailAdminModelForm or the base_form_class attribute - these are used by pages, snippets and ModelAdmin to support Wagtail-specific features like inline children and panels, but images work through plain Django models and forms.
You can customise the form by subclassing BaseImageForm and setting WAGTAILIMAGES_IMAGE_FORM_BASE in your project settings. As long as you define your form class somewhere outside of models.py (e.g. in a separate forms.py module), you'll avoid the circular dependency that leads to the "Models aren't loaded yet" error.

How to trigger custom python code when a valid django form is saved in the database

Django newbie here, I created a simple form following this tutorial, and my form correctly saves the data in my Postgres connected local database. I was wondering, how can I trigger a function, whenever a valid form is saved into the database? The code I want to run is a simple function which is written in a python file, and it does some processing on the latest data given by the first form. I want it to run only when a valid form data is saved and was wondering if django signal trigger is my way to go. Feel free to ask for any further clarification. In other words, I want to do some post-processing on data, which is present inside the database, which is being filled by the form, and trigger the post-processing only when valid data is entered in the database.
Here is my code :
views.py
from django.shortcuts import render
from django.http import HttpResponse
from .forms import auditform, ClientAuditForm
from django.db.models.signals import post_save
from . import rocode
# def auditingfun(request):
# return HttpResponse('Auditing form works')
# # Create your views here.
def auditingfun(request):
if request.method == 'POST':
forminput = auditform(request.POST)
if forminput.is_valid():
Name = forminput.cleaned_data['Name']
Origin = forminput.cleaned_data['Origin']
ClientAddress = forminput.cleaned_data['ClientAddress']
DispatchType = forminput.cleaned_data['DispatchType']
ETA = forminput.cleaned_data['ETA']
GSTIN = forminput.cleaned_data['GSTIN']
# print(GSTIN,Name,Origin,Destination,MaterialType,Preference,ClientAddress,DispatchType,ETA)
forminput = auditform(request.POST)
return render(request, 'auditing/auditform.html', {'forminput': forminput} )
forms.py
from django import forms
from .models import auditModel
class auditform(forms.Form):
Origin = forms.CharField()
Destination = forms.CharField()
MaterialType = forms.CharField()
Preference = forms.CharField()
ClientAddress = forms.CharField(widget=forms.Textarea)
Name = forms.CharField()
GSTIN = forms.IntegerField()
DispatchType = forms.ChoiceField(choices=[('Question','Inbound'),('Other','Outbound')])
ETA = forms.CharField()
class ClientAuditForm(forms.ModelForm):
class Meta:
model = auditModel
fields = ('Origin','Destination','MaterialType','GSTIN','Name','Preference','ClientAddress','DispatchType','ETA')
Just for simplicity, imagine the customcode (imported in the views.py file as rocode.py) I have just adds the data entered and stores the data in the same database, in a different column.
what you want to use here is signals. A signal is some function that gets executed after an item is added or updated in your data base. Assuming your model you want to connect to is called "MyModel" do this:
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(post_save, sender=MyModel)
def my_handler(sender, instance, created, **kwargs):
if created:
# run your custom code HERE
instance is what was inserted / updated, created is boolean indicating if this was an update or insert.
docs: https://docs.djangoproject.com/en/2.1/topics/signals/

Django Naturaltime is not working in .annotate

Here I just wants to annotate a field on a model that gives human readable format saying how much time elapsed since it's created
My Model is created 30 seconds ago
My Model Description:
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
#property
def natural_time(self):
return naturaltime(self.created_at)
What I did is here
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.db.models import F
from .models import MyModel
m = MyModel.objects.annotate(timesincecreated=naturaltime(F('created_at'))
print m.values('timesincecreated')
on this print call I am getting the DateTimeField that I used in the model.
But If I want to access the property.
from .models import MyModel
m= MyModel.objects.first()
print m.natural_time
It works.
Any help? TIA.
You cannot use naturaltime function for annotation, annotation is a computation that is done on database level.
Django provides only a set of basic computations which can be processed by the database like Count, Sum, Min, Max, etc. You can refer to official doc to learn more about Query Expressions.

Set default date with Django SelectDateWidget

Firstly, this question has already been asked here, however the answer does not work, and is also for Django 1.3. Having done extensive research on similar questions on SO, the Django Docs, and general Googling, I still can't find a working answer. Not really a major detail, but it's annoying both when trying to use the form I'm creating, and because I can't solve it.
When using the SelectDateWidget in a ModelForm, I want to be able to set the default on the widget to today's date.
forms.py
from django import forms
from .models import Article
from django.utils import timezone
class ArticleForm(forms.ModelForm):
publish=forms.DateField(widget=forms.SelectDateWidget(), initial = timezone.now)
class Meta:
model = Article
fields = [
'title',
...
'publish',
]
models.py
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils import timezone
from Blog.models import Category
class Article(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length = 120, unique=True)
...
publish = models.DateField(default=timezone.now)
I'm assuming I have some syntax error somewhere, but have had no joy in finding it.
Looks like there's a subtle difference when setting the the default time for DateTimeField and DateField.
DateField.auto_now_add Automatically set the field to now when the
object is first created. Useful for creation of timestamps. Note that
the current date is always used; it’s not just a default value that
you can override. So even if you set a value for this field when
creating the object, it will be ignored. If you want to be able to
modify this field, set the following instead of auto_now_add=True:
For DateField: default=date.today - from datetime.date.today()
For DateTimeField: default=timezone.now - from django.utils.timezone.now()
So, you'll want to use date.today rather than timezone.now.

Django error_messages not working in my model but works in my view?

So i'm simply trying to pass out my own custom error which works in my view here below:
from django.shortcuts import render
from django.http import HttpResponse
from TasksManager.models import Supervisor, Developer
from django import forms
class someView(forms.Form):
error_name = {'required':'You must type a name !','invalid':'Wrong format.'}
name = forms.CharField(label="Name", max_length=30, error_messages=error_name)
but when i try to do it in a model it just ignores the custom error_message i'm passing through to it and uses djangos built in error_message code below:
from django.db import models
class someModel(models.Model):
error_name = {'required':'You must type a name !','invalid':'Wrong format.'}
name = models.CharField(max_length=50, verbose_name="Name", error_messages=error_name)
What could i do to make this work in my model(how do i make my 2nd block of code work)?
According to the docs, the available error types are null, blank, invalid, invalid_choice, unique, and unique_for_date. There is no 'required' error type.
https://docs.djangoproject.com/en/dev/ref/models/fields/#error-messages

Categories