Django form test fails - django

I'm trying to perform a simple test on my form to confirm that it's not valid when there is no data given and is valid when data is given.
When running tests with pytest (py.test) the test with no data works fine but I'm getting this error for the test with data present:
AssertionError: Should be valid if data is given
E assert False is True
E + where False = <bound method BaseForm.is_valid of <PostForm bound=True, valid=False, fields=(title;content;author;image;published;draft;category;read_time)>>()
E + where <bound method BaseForm.is_valid of <PostForm bound=True, valid=False, fields=(title;content;author;image;published;draft;category;read_time)>> = <PostForm bound=True, valid=False, fields=(title;content;author;image;published;draft;category;read_time)>.is_valid
posts/tests/test_forms.py:21: AssertionError
my models.py:
from django.db import models
from django.core.urlresolvers import reverse
from django.conf import settings
from django.db.models.signals import pre_save
from django.utils import timezone
from django.utils.text import slugify
from .utils import read_time
class Category(models.Model):
name = models.CharField(max_length=120, unique=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.id: # to prevent changing slug on updates
self.slug = slugify(self.name)
return super(Category, self).save(*args, **kwargs)
def upload_location(instance, filename):
return '%s/%s'%(instance.id, filename)
class PostManager(models.Manager):
def active(self):
return super(PostManager, self).filter(draft=False, published__lte=timezone.now())
class Post(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(unique=True)
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
published = models.DateField(auto_now=False, auto_now_add=False)
draft = models.BooleanField(default=False)
category = models.ManyToManyField(Category)
read_time = models.IntegerField(default=0)
objects = PostManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('posts:detail', kwargs={'pk': self.pk})
def save_no_img(self):
self.image = None
return super(Post, self).save()
def create_slug(instance, new_slug=None):
slug = slugify(instance.title)
if new_slug is not None:
slug = new_slug
qs = Post.objects.filter(slug=slug).order_by("-id")
exists = qs.exists()
if exists:
new_slug = "%s-%s" %(slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
def pre_save_post_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = create_slug(instance)
html_content = instance.content
instance.read_time = read_time(html_content)
pre_save.connect(pre_save_post_receiver, sender=Post)
my forms.py:
from django import forms
from .models import Post
from pagedown.widgets import PagedownWidget
class PostForm(forms.ModelForm):
published = forms.DateField(widget=forms.SelectDateWidget)
content = forms.CharField(widget=PagedownWidget())
class Meta:
model = Post
# fields = ['author', 'title', 'content', 'image', 'draft', 'published', 'category']
exclude = ['objects', 'updated', 'timestamp', 'slug']
test_forms.py:
import pytest
from .. import forms
from posts.models import Category
from mixer.backend.django import mixer
pytestmark = pytest.mark.django_db
class TestPostForm():
def test_empty_form(self):
form = forms.PostForm(data={})
assert form.is_valid() is False, 'Should be invalid if no data is given'
def test_not_empty_form(self):
staff_user = mixer.blend('auth.User', is_staff=True)
category = mixer.blend('posts.Category')
data={'content': 'some content',
'author': staff_user,
'title': 'some title',
'category': category,}
form = forms.PostForm(data=data)
assert form.is_valid() is True, 'Should be valid if data is given'
update:
collected more specific errors using:
assert form.errors == {}, 'should be empty'
errors:
{'author': ['Select a valid choice. That choice is not one of the
available choices.'],
'category': ['Enter a list of values.'],
'published': ['This field is required.'],
'read_time': ['This field is required.']}
how to address them?
update 2:
as Nadège suggested I modified data to include published and read_time, changed category into a list and created a user without mixer.
staff_user = User.objects.create_superuser(is_staff=True,
email='oo#gm.com',
username='staffuser',
password='somepass')
category = mixer.blend('posts.Category')
today = date.today()
data={'content': 'some content',
'author': staff_user,
'title': 'some title',
'published': today,
'read_time': 1,
'category': [category],}
There is still error regarding the 'author':
{'author': ['Select a valid choice. That choice is not one of the
available choices.']}
update 3:
for some reason 'author' had to be provided as an id, the working code for this test looks like this:
class TestPostForm():
def test_empty_form(self):
form = forms.PostForm(data={})
assert form.is_valid() is False, 'Should be invalid if no data is given'
def test_not_empty_form(self):
staff_user = mixer.blend('auth.User')
category = mixer.blend('posts.Category')
today = date.today()
data={'content': 'some content',
'author': staff_user.id,
'title': 'some title',
'published': today,
'read_time': 1,
'category': [category],}
form = forms.PostForm(data=data)
assert form.errors == {}, 'shoud be empty'
assert form.is_valid() is True, 'Should be valid if data is given'

Ok so when you have an invalid form, first thing is to check why, so the errors of the form. With this new information we can fix each problem.
Your form has 4 validations errors. The last two are pretty straightforward.
'published': ['This field is required.'],
'read_time': ['This field is required.']
Those two fields in your form are required but you didn't filled them.
So you have two options,
Add a value for those fields in the data you give to the form
Remove the fields from the form: add them to exclude
You can also set the published field a not required like this:
published = forms.DateField(widget=forms.SelectDateWidget, required=False)
for read_time, the field is required or not, depending on the corresponding field in the model. If the model field is not nullable, the field in the form is set as required.
Next there is
'category': ['Enter a list of values.']
You provided a value but the type is not what was expected.
category in your model is ManyToMany so you can't give just one category, it must be a list (even if it has only one element!)
'category': [category],
Finally the author,
'author': ['Select a valid choice. That choice is not one of the available choices.']
There too you provided a value that is not valid. The validation doesn't recognize the value as a proper auth.User. I'm not familiar with Mixer, so maybe ask a new question specifically about Mixer and Django Forms with a ForeignKey.

Related

AssertionError: 200 != 302. Trying to test a redirect from a successful form entry

I'm trying to test redirect from a form using POST. I'm following the mdn web doc tutorial for Django and thought I would see if I could do some of my own test which are not in the tutorial. I'm try to test the book creation form in the tutorial to see if it redirects to book details page which it should do automatically because it's a class based generic view. It works correctly when I test locally but I cannot get the testcase to pass. Thanks for the help in advance.
This is the error:
> =====================================================================
FAIL: test_redirects_to_book_details_on_success (catalog.tests.test_views.BookCreateView)
> ----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\makor\mypythonscripts\django_projects\locallibrary\catalog\tests\test_views.py", line 399, in test_redirects_to_book_details_on_success
self.assertRedirects(response, reverse('book-detail', kwargs={'pk': 1}))
File "C:\Users\makor\mypythonscripts\django_projects\venv\lib\site-packages\django\test\testcases.py", line 512, in assertRedirects
self.assertEqual(
AssertionError: 200 != 302 : Response didn't redirect as expected: Response code was 200 (expected 302)
----------------------------------------------------------------------
Ran 1 test in 0.341s
FAILED (failures=1)
Destroying test database for alias 'default'...
models.py
from django.db import models
from django.urls import reverse # Used to generate URLs by reversing the URL pattern.
from django.contrib.auth.models import User
from datetime import date
import uuid # Required for unique book instances
class Genre(models.Model):
"""Model representing book genre."""
name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
def __str__(self):
"""String representation of Model object."""
return self.name
class Language(models.Model):
"""Model representing a langauge."""
LANGUAGES = (
('English', 'English'),
('French', 'French'),
('German', 'German'),
('Spanish', 'Spanish'),
)
language = models.CharField(max_length=20, choices=LANGUAGES, blank=False, default='English', help_text='Availble languages')
def __str__(self):
"""String representation of Model object."""
return self.language
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
# Foreign Key used because book can only have one author, but author can have multiply books.
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book.')
isbn = models.CharField('ISBN', max_length=13, unique=True,
help_text='13 character ISBN number')
# ManyToManyField used because genre can contain many books. Books can cover many genres.
# Genre class has already been defined, so we can specify the object above.
genre = models.ManyToManyField('Genre', help_text='Select a genre for this book.')
language = models.ManyToManyField('Language', help_text='Select the langauge.')
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""Returns the URL to access a detail record for this book."""
return reverse('book-detail', args=[str(self.id)])
def display_genre(self):
"""Create a string for the Genre. This is required to display genre in Admin."""
return ', '.join(genre.name for genre in self.genre.all()[:3])
display_genre.short_description = 'Genre'
class BookInstance(models.Model):
"""Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4,
help_text='Unique ID for this particular book across the whole library.')
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200, blank=True)
due_back = models.DateField(null=True, blank=True)
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On loan'),
('a', 'Available'),
('r', 'Reserved'),
)
status = models.CharField(
max_length=1,
choices=LOAN_STATUS,
blank=True,
default='m',
help_text='Book availability',
)
class Meta:
ordering = ['due_back']
permissions = (('can_mark_returned', 'set book as returned'),)
def __str__(self):
"""String representing the Model object."""
return f'{self.id} ({self.book.title})'
#property
def is_overdue(self):
"""Determines if the book is overdue based on due date and current date."""
return bool(self.due_back and date.today() > self.due_back)
class Author(models.Model):
"""Model representing an author."""
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('died', null=True, blank=True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
"""Returns the URL to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String representing the Model object."""
return f'{self.last_name}, {self.first_name}'
views.py
import datetime
from django.views import generic
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse, reverse_lazy
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.decorators import login_required, permission_required
from . models import Book, Author, BookInstance, Genre, Language
from . forms import RenewBookForm
def index(request):
"""View function for home page of site."""
# Generate counts of some main objects.
num_books = Book.objects.all().count()
num_instances = BookInstance.objects.all().count()
# Available books (status = 'a')
num_instances_available = BookInstance.objects.filter(status__exact='a').count()
# The 'all()' is implied by default.
num_authors = Author.objects.count()
# Number of visits to this view, as counted in the session variable.
num_visits = request.session.get('num_visits', 0)
request.session['num_visits'] = num_visits + 1
# Generate counts for genre.
num_genre = Genre.objects.all().count()
# Filter books containing a particular word.
title = 'game of thrones'
num_title = Book.objects.filter(title__contains=title.lower()).count()
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
'num_genre': num_genre,
'num_title': num_title,
'num_visits': num_visits,
}
# Render the HTML template index.html with the data in the context variable.
return render(request, 'index.html', context=context)
class BookListView(generic.ListView):
model = Book
paginate_by = 10
class BookDetailView(generic.DetailView):
model = Book
class AuthorListView(generic.ListView):
model = Author
paginate_by = 10
class AuthorDetailView(generic.DetailView):
model = Author
class LoanedBooksByUserListView(LoginRequiredMixin, generic.ListView):
"""Generic class-based view listing books on loan to current user."""
model = BookInstance
template_name = 'catalog/bookinstance_list_borrowed_user.html'
paginate_by = 10
def get_queryset(self):
return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')
class BorrowedListView(PermissionRequiredMixin, generic.ListView):
"""Generic class-based view listing all books, requires correct permissions."""
permission_required = 'catalog.can_mark_returned'
model = BookInstance
template_name = 'catalog/borrowed_list.html'
paginate_by = 10
def get_queryset(self):
return BookInstance.objects.filter(status__exact='o').order_by('due_back')
#login_required
#permission_required('catalog.can_mark_returned', raise_exception=True)
def renew_book_librarian(request, pk):
book_instance = get_object_or_404(BookInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewBookForm(request.POST)
# Check if the form is valid
if form.is_valid():
# Process the data in form.cleaned_data as required (here we just write it to the model due_back field)
book_instance.due_back = form.cleaned_data['renewal_date']
book_instance.save()
# Redirect to a new URL:
return HttpResponseRedirect(reverse('all-borrowed'))
else:
context = {
'form': form,
'book_instance': book_instance,
}
return render(request, 'catalog/book_renew_librarian.html', context)
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})
context = {
'form': form,
'book_instance': book_instance,
}
return render(request, 'catalog/book_renew_librarian.html', context)
class AuthorCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
permission_required = 'catalog.can_mark_returned'
model = Author
fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death']
initial = {'date_of_death': '11/06/2022'}
class AuthorUpdate(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
permission_required = 'catalog.can_mark_returned'
model = Author
fields = '__all__' # Not recommended (potential security issue if more fields are added.)
class AuthorDelete(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
permission_required = 'catalog.can_mark_returned'
model = Author
success_url = reverse_lazy('authors')
class BookCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
permission_required = 'catalog.can_mark_returned'
model = Book
fields = ['title', 'author', 'summary', 'isbn', 'genre', 'language']
class BookUpdate(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
permission_required = 'catalog.can_mark_returned'
model = Book
fields = '__all__'
class BookDelete(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
permission_required = 'catalog.can_mark_returned'
model = Book
success_url = reverse_lazy('books')
test.py
class BookCreateView(TestCase):
def setUp(self):
# Create a user
test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD')
test_user1.save()
test_user2.save()
# Give test_user2 permission to create Book.
permission = Permission.objects.get(name='set book as returned')
test_user2.user_permissions.add(permission)
test_user2.save()
test_author = Author.objects.create(first_name='John', last_name='Smith')
test_genre = Genre.objects.create(name='Fantasy')
test_language = Language.objects.create(language='English')
test_author.save()
test_genre.save()
test_language.save()
self.author = Author.objects.get(id=1)
self.genre = Genre.objects.get(id=1)
self.language = Language.objects.get(id=1)
def test_redirect_if_not_logged_in(self):
response = self.client.get(reverse('book-create'))
# Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.startswith('/accounts/login/'))
def test_forbidden_if_logged_in_but_not_correct_permission(self):
login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
response = self.client.get(reverse('book-create'))
self.assertEqual(response.status_code, 403)
def test_logged_in_with_permission_to_create_book(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('book-create'))
# Check that it lets us login - this is our book and we have the right permissions.
self.assertEqual(response.status_code, 200)
def test_view_uses_correct_template(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('book-create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'catalog/book_form.html')
def test_redirects_to_book_details_on_success(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.post(reverse('book-create'), {'title': 'Test',
'author': self.author,
'summary': 'It\'s all good',
'isbn': '1234567',
'genre': self.genre,
'language': self.language,
})
self.assertRedirects(response, reverse('book-detail', kwargs={'pk': 1}))
form template
{% extends 'base_generic.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit" />
</form>
{% endblock %}
Screenshot of form
I have been at this for about two days but I think my beginners luck has ran out. I can usually figure it out by myself after a while but not anymore as this is my first post. I think the data i am entering into the client.post is not working and giving the correct response object. Sorry if my lingo is not correct as I am new to all this.

django test a modelform - ValidationError not a valid UUID

I am testing a modelform and getting a ValidationError. My model, view and test are as follows:
model
class Course(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
course_name = models.CharField(max_length=30)
grade_level = models.CharField(max_length=4, default="SEC")
view
# method_decorator([login_required, teacher_required], name='dispatch')
class CourseUpdateView(PermissionRequiredMixin, UpdateView):
raise_exceptions = True
permission_required = 'gradebook.change_course'
permission_denied_message = "You don't have access to this."
model = Course
fields = ['course_name', 'grade_level', ]
template_name_suffix = '_update'
def get_success_url(self, *args, **kwargs):
return reverse('gradebook:coursedetail', kwargs={'course_pk': self.object.pk})
form
class CourseForm(ModelForm):
class Meta:
model = Course
fields = ('course_name', 'grade_level',)
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.qc = Course.objects.filter(user=user)
def clean(self):
super(CourseForm, self).clean()
course_name = self.cleaned_data.get('course_name')
if course_name and self.qc.filter(course_name__iexact=course_name).exists():
raise ValidationError("A course with that name already exists.")
if len(course_name) > 20:
if len(course_name) > 10:
raise ValidationError(
"Your course name cannot be longer than 20 characters")
return self.cleaned_data
Test
class CourseUpdateTests(TestCase):
#classmethod
def setUpTestData(cls):
cls.user = CustomUser.objects.create_user(
username='tester',
email='tester#email.com',
password='tester123',
is_teacher=True,
is_active=True,
)
cls.user.save()
def test_CourseUpdate_valid(self):
request = HttpRequest()
request.POST = {
'user': self.user,
'id': '4d192045-07fa-477f-bac2-5a99fe2e7d46',
'course_name': "Science",
'grade_level': "SEC"
}
form = CourseForm(request.POST)
self.assertTrue(form.is_valid())
The error I get:
Raise exceptions.ValidationError(
django.core.exceptions.ValidationError: ["“{'user': <CustomUser: tester>, 'id': '4d192045-07fa-477f-bac2-5a99fe2e7d46', 'course_name': 'Science', 'grade_level': 'SEC'}” is not a valid UUID."]
I have tried not putting the id in the request.POST but get the same error.
I originally tried to test for a valid form by using:
def test_CourseUpdate_valid(self):
form = CourseForm(data={
'user': self.user,
'id': '4d192045-07fa-477f-bac2-5a99fe2e7c04',
'course_name': "Science",
'grade_level': "SEC"
},)
self.assertTrue(form.is_valid())
This did not work though, giving me the error TypeError: __init__() missing 1 required positional argument: 'user'
Your original solution was not good because you were missing the user positional argument in the form init function.
Secondly, your CourseForm class should specify the rest of the fields (id, and user) if you want to pass them to the form.
You could probably just not pass id and user to the CourseForm data in the test as they aren't relevant.
This should work:
def test_CourseUpdate_valid(self):
form = CourseForm(self.user, data={
'course_name': "Science",
'grade_level': "SEC"
},)
self.assertTrue(form.is_valid())
Can you try that and let me know if the problem persists?

Django's get_initial() method not working as desired

I am using django's generic CreateView to build a comment system for my site. A user is allowed to do comment for a movie. Here is my Comment model-
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="comments", on_delete=models.CASCADE)
body = models.TextField()
movie = models.ForeignKey(Movie, related_name="comments", on_delete=models.CASCADE)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
class Meta:
ordering = ('created',)
def __str__(self):
return "comment by {} on {}".format(self.user.first_name, self.movie)
Here is the CreateView i am using-
class AddComment(LoginRequiredMixin, CreateView):
form_class = CommentForm
def get_initial(self):
initial = super().get_initial()
#for providing initial values to the form
initial['user'] = self.request.user.id
initial['movie'] = self.kwargs['movie_id']
return initial
def get_success_url(self):
movie_id = self.kwargs['movie_id']
return reverse('detail', kwargs={'pk':movie_id})
def render_to_response(self, context=None, **response_kwargs):
movie_id = self.kwargs['movie_id']
return redirect(to = reverse('detail', kwargs={'pk':movie_id}))
Here is the commentform -
class CommentForm(forms.ModelForm):
user = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=get_user_model().objects.all())
movie = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=Movie.objects.all())
class Meta:
model = Comment
fields = ('user','movie', 'body')
I am trying to associate a comment to a user and a movie. I thus used get_initial() method to fill the form with initial data because user and movie were not present in the posted data. But somehow always form.is_valid() turns out to be false. I don't know where i went wrong. Please Help.
If it helps i tried to debug my program by printing out the value of kwargs that were being used to instantiate the form object by overriding the get_form_kwargs function-
{
'initial': {'user': 1, 'movie': 2}, 'prefix': None,
'data': <QueryDict: {'csrfmiddlewaretoken': ['wFmkOMLAcIszMc17GsBsqPhyaZnJEXb0TRNteKd9sgjYKEF3jvqwsQ3Noik3DHq6'], 'body': ['best movie ever\r\n'], 'user': [''], 'movie': ['']}>, 'files': <MultiValueDict: {}>
}
Well, user and movie are Foreign key fields, so that they expect to receive object of related models as initials. You are trying to use pk(int) instead of these objects.
It should be like following:
def get_initial(self):
initial = super().get_initial()
#for providing initial values to the form
initial['user'] = self.request.user
initial['movie'] = Movie.objects.get(pk=movie_id)
return initial.copy()

Django SlugField not unique

I'm stuck from a couple hours and I can't solve this problem.
The following code works well, but if I write a "Title" that already exist I get:
UNIQUE constraint failed: appname_shopaccount.url_shop
Model
class ShopAccount(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=150)
url_shop = models.SlugField(max_length=200, unique=True)
company_name = models.CharField(max_length=200)
def save(self, *args, **kwargs):
self.url_shop = slugify(self.title)
super(ShopAccount, self).save(*args, **kwargs)
def __str__(self):
return self.title
Forms
class SignUpShopForm(ModelForm):
class Meta:
model = ShopAccount
fields=['title', 'company_name']
exclude= ('user',)
error_messages = {
'title': {
'required': "Enter a Shop name",
},
'company_name': {
'required': "Enter a Company name",
}
}
View
def signup_shop(request):
if request.POST:
form = SignUpShopForm(request.POST)
if form.is_valid():
account = form.save(commit=False)
account.user = request.user
account.save()
return HttpResponseRedirect('/account/updated/')
else:
form = SignUpShopForm(data=request.POST)
return render_to_response('register_shop.html', { 'form':form }, context_instance=RequestContext(request))
else:
return render_to_response('register_shop.html', context_instance=RequestContext(request))
How can I solve this problem?
Remove unique=True from the url_shop field in your model and update your database with python manage.py makemigrations and python manage.py migrate.
Be aware though that a slug field that can have non-unique values might have consequences for your app depending on how you are using it (two objects having the same url for example)
You can use unique_slugify to force a unique slug- https://djangosnippets.org/snippets/690/
import unique_slugify
class ShopAccount(models.Model):
...
def save(self, *args, **kwargs):
self.url_shop = unique_slugify(self, self.title, slug_field_name='url_shop')
super(ShopAccount, self).save(*args, **kwargs)
If the slug being created already exists, unique_slugify will append a '-1' (and on upwards) to the slug until a unique slug is found. Keep unique=True in your url_shop model kwargs :]
The reason you have an issue with your existing code is that slugify only converts the title field to a slug, not checking for existing slug values. With the method above, unique_slugify will check existing values and generate a slug that does not exist in the db yet.

upload an images using django and tastypie (python version : 2.7)

I am having a hard time figuring out a way of doing multipart/formencoded image upload using django and tastypie. I went through all the possible answers on stackoverflow but cant seem to find a solution that would work. Also, i am a beginner in python so cant seem to understand a lot of stuff. I have written some code and would like someone to point me as to what am i doing wrong.
Models.py
import random
from django.db import models
from django.template.loader import get_template
from django.template import Context
import suuq.settings
from usermanagement import utils
from django.contrib.auth.models import UserManager
from django.db.models import ImageField
class adManager(models.Manager):
def create_ad(self, category, title, email, tag, location, address, description, phone, img):
if not category:
raise ValueError('add must have a category')
if not title:
raise ValueError('add must have a title')
if not email:
raise ValueError('ad must have a email')
if not tag:
raise ValueError('ad must have a tag')
if not location:
raise ValueError('ad must have a location')
if not address:
raise ValueError('ad must have a address')
if not description:
raise ValueError('ad must have a description')
if not phone:
raise ValueError('ad must have a phone number')
if not img:
raise ValueError('ad must have a image')
ad = self.create(category = category, title = title, email = UserManager.normalize_email(email).strip().lower(),
tag = tag, location = location, address = address, description = description, phone = phone, img=img)
ad.save()
return ad
class Ad(models.Model):
objects = adManager()
category = models.CharField(max_length=100)
title = models.CharField(max_length=200)
email = models.EmailField(
max_length=255,
unique=True,
)
tag = models.CharField(max_length=200)
location = models.CharField(max_length=50)
address = models.CharField(max_length=200)
description = models.CharField(max_length=140)
phone = models.CharField(max_length=10, null=True)
img = models.ImageField(upload_to="img", null=True, blank=True)
api.py
import json
from django.conf.urls import url
from django import forms
from tastypie.authentication import SessionAuthentication
from tastypie.authorization import Authorization
from tastypie.exceptions import Unauthorized
from tastypie.resources import ModelResource
from tastypie.throttle import CacheThrottle
from tastypie.utils import trailing_slash
from tastypie.validation import FormValidation
from django.db.models import FileField
from tastypie import http, fields
from PIL import Image
from ad.models import Ad
class MultipartResource(object):
def deserialize(self, request, data, format=None):
if not format:
format = request.META.get('CONTENT_TYPE', 'application/json')
if format == 'application/x-www-form-urlencoded':
return request.POST
if format.startswith('multipart'):
data = request.POST.copy()
data.update(request.FILES)
return data
return super(MultipartResource, self).deserialize(request, data, format)
class AdResource(ModelResource, MultipartResource):
img = fields.FileField(attribute="img", null=True, blank=True)
class Meta:
queryset = Ad.objects.all()
resource_name = 'ad'
fields = ['id', 'category', 'title','email','tag','location', 'address', 'description', 'phone', 'img']
allowed_methods = ['post','get','delete']
include_resource_uri = False
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/add%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('add_ad'), name="add_ad"),
]
def add_ad(self, request, **kwargs):
self.method_check(request, allowed=['post'])
data = json.loads(request.body)
category = data.get('category', '')
title = data.get('title', '')
email = data.get('email', '')
tag = data.get('tag', '')
location = data.get('location', '')
address = data.get('address', '')
description = data.get('description', '')
phone = data.get('phone', '')
if(request.method == 'POST'):
if "multipart/form-data" not in str(request.META['CONTENT_TYPE']):
return
else:
if('img' in request.FILES):
upload = request.FILES['img']
img = Image.open(upload)
return
else:
return
else:
return
ad = Ad.objects.create_ad(category=category, title=title, email=email, tag=tag,
location=location, address=address, description=description, phone=phone, img=img)
return self.create_response(request, {
'success': True,
'ad_id': ad.id,
'message': 'ad address successfully added'
})
I know my code is not indented properly but i have it properly indented on my dev end. Please help me fix my logic, i am really stuck.
I found the problem and here is my updated solution using multipart form data
class AdResource(ModelResource):
img = fields.FileField(attribute="img", null=True, blank=True)
class Meta:
queryset = Ad.objects.all()
resource_name = 'ad'
fields = ['id', 'category', 'title','email','tag','location', 'address', 'description', 'phone', 'img']
allowed_methods = ['post','get','delete']
include_resource_uri = False
always_return_data = True
limit = 0
authentication = SessionAuthentication()
authorization = AdAuthorization()
throttle = CacheThrottle(throttle_at=200)
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/add%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('add_ad'), name="add_ad"),
]
def add_ad(self, request, **kwargs):
self.is_authenticated(request)
self.method_check(request, allowed=['post'])
format = request.META.get('CONTENT_TYPE')
if format.startswith('multipart'):
data = request.POST.copy()
data.update(request.FILES)
category = data.get('category', '')
title = data.get('title', '')
email = data.get('email', '')
tag = data.get('tag', '')
location = data.get('location', '')
address = data.get('address', '')
description = data.get('description', '')
phone = data.get('phone', '')
img = data.get('img','')
ad = Ad.objects.create_ad(user=request.user, category=category, title=title, email=email, tag=tag,
location=location, address=address, description=description, phone=phone, img=img)
return self.create_response(request, {
'success': True,
'message': 'Ad address successfully added',
'id': ad.id
})
else:
return self.create_response(request, {
'success': False,
'message': 'Invalid format'
})