Related
I am working on a simple "issue tracking" web application as way to learn more about Django.
I am using Django 4.1.4 and Python 3.9.2.
I have the following classes in models.py (which may look familiar to people familiar with JIRA):
Components
Issues
IssueStates
IssueTypes
Priorities
Projects
Releases
Sprints
Originally I also had a Users class in models.py but now am trying to switch to using the Django User model. (The User class no longer exists in my models.py)
I have been studying the following pages to learn how best to migrate to using the Django Users model.
Django Best Practices: Referencing the User Model
Referencing the User Model
All of my List/Detail/Create/Delete view classes worked fine with all of the above models until I started working on using the Django User class.
-- models.py --
from django.conf import settings
class Issues(models.Model):
id = models.BigAutoField(primary_key=True)
project = models.ForeignKey(
to=Projects, on_delete=models.RESTRICT, blank=True, null=True
)
summary = models.CharField(max_length=80, blank=False, null=False, default="")
issue_type = models.ForeignKey(
to=IssueTypes, on_delete=models.RESTRICT, blank=True, null=True
)
issue_state = models.ForeignKey(
to=IssueStates, on_delete=models.RESTRICT, blank=True, null=True, default="New"
)
# https://learndjango.com/tutorials/django-best-practices-referencing-user-model
# https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#referencing-the-user-model
reporter = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.RESTRICT,
related_name="reporter_id",
)
priority = models.ForeignKey(
to=Priorities, on_delete=models.RESTRICT, blank=True, null=True
)
component = models.ForeignKey(
to=Components, on_delete=models.RESTRICT, blank=True, null=True
)
description = models.TextField(blank=True, null=True)
planned_release = models.ForeignKey(
to=Releases, on_delete=models.RESTRICT, blank=True, null=True
)
# https://learndjango.com/tutorials/django-best-practices-referencing-user-model
# https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#referencing-the-user-model
assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.RESTRICT,
related_name="assignee_id",
)
slug = models.ForeignKey(
to="IssueSlugs", on_delete=models.RESTRICT, blank=True, null=True
)
sprint = models.ForeignKey(
to=Sprints, on_delete=models.RESTRICT, blank=True, null=True
)
def save(self, *args, **kwargs):
if not self.slug:
# generate slug for this new Issue
slug = IssueSlugs()
slug.project_id = self.project.id
slug.save()
self.slug = slug
super().save(*args, **kwargs)
def __str__(self):
return self.slug.__str__() + " - " + self.summary.__str__()
class Meta:
managed = True
db_table = "issues"
class IssueSlugs(models.Model):
"""
This table is used to generate unique identifiers for records in the
Issues table. My goal was to model the default behavior found in JIRA
where each Issue has a unique identifier that is a combination of:
1) the project abbreviation
2) a sequential number for the project
So here when creating a new Issue record, if it is the first record for
a particular project, the sequential number starts at 100, otherwise it
is the next sequential number for the project.
"""
id = models.BigAutoField(primary_key=True)
project = models.ForeignKey(
to=Projects, on_delete=models.RESTRICT, blank=True, null=True
)
slug_id = models.IntegerField(default=100)
slug = models.CharField(
max_length=80,
blank=False,
null=False,
unique=True,
)
def __str__(self):
return self.slug.__str__()
def save(self, *args, **kwargs):
if not self.slug:
result = IssueSlugs.objects.filter(
project_id__exact=self.project.id
).aggregate(Max("slug_id"))
# The first issue being created for the project
# {'slug_id__max': None}
if not result["slug_id__max"]:
self.slug_id = 100
self.slug = self.project.abbreviation + "-" + str(100)
else:
logging.debug(result)
next_slug_id = result["slug_id__max"] + 1
self.slug_id = next_slug_id
self.slug = self.project.abbreviation + "-" + str(next_slug_id)
super().save(*args, **kwargs)
class Meta:
managed = True
db_table = "issue_slugs"
-- issues.py --
class CreateUpdateIssueForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# save for IssueCreateView.form_valid()
self.kwargs = kwargs
font_size = "12pt"
for field_name in self.fields:
if field_name in ("summary", "description"):
self.fields[field_name].widget.attrs.update(
{
"size": self.fields[field_name].max_length,
"style": "font-size: {0}".format(font_size),
}
)
elif field_name in ("reporter", "assignee"):
# https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#referencing-the-user-model
User = get_user_model()
choices = list()
choices.append(("", ""))
for element in [
{
"id": getattr(row, "id"),
"display": row.get_full_name(),
}
for row in User.objects.exclude(is_superuser__exact="t")
]:
choices.append((element["id"], element["display"]))
self.fields[field_name] = forms.fields.ChoiceField(
choices=choices,
# I had to specify required=False here to eliminate a very
# strange error:
# An invalid form control with name='assignee' is not focusable.
required=False,
)
else:
# all the <select> fields ...
self.fields[field_name].widget.attrs.update(
{
"class": ".my-select",
}
)
class Meta:
model = Issues
fields = [
"project",
"summary",
"component",
"description",
"issue_type",
"issue_state",
"reporter",
"priority",
"planned_release",
"assignee",
"sprint",
]
class IssueCreateView(LoginRequiredMixin, PermissionRequiredMixin, generic.CreateView):
"""
A view that displays a form for creating an object, redisplaying the form
with validation errors (if there are any) and saving the object.
https://docs.djangoproject.com/en/4.1/ref/class-based-views/generic-editing/#createview
"""
model = Issues
permission_required = "ui.add_{0}".format(model.__name__.lower())
template_name = "ui/issues/issue_create.html"
success_url = "/ui/issue_list"
form_class = CreateUpdateIssueForm
def form_valid(self, form):
User = get_user_model()
if "reporter" in self.kwargs:
form.instance.reporter = User.objects.get(id__exact=self.kwargs["reporter"])
if not form.is_valid():
messages.add_message(
self.request, messages.ERROR, "ERROR: '{0}'.".format(form.errors)
)
return super().form_valid(form)
action = self.request.POST["action"]
if action == "Cancel":
# https://docs.djangoproject.com/en/4.1/topics/http/shortcuts/#django.shortcuts.redirect
return redirect("/ui/issue_list")
return super().form_valid(form)
def get_initial(self):
"""
When creating a new Issue I'm setting default values for a few
fields on the Create Issue page.
"""
# https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#referencing-the-user-model
User = get_user_model()
from ui.models import IssueStates, Priorities, IssueTypes
issue_state = IssueStates.objects.get(state__exact="New")
priority = Priorities.objects.get(priority__exact="Medium")
issue_type = IssueTypes.objects.get(issue_type__exact="Task")
reporter = User.objects.get(username__exact=self.request.user)
return {
"issue_state": issue_state.id,
"priority": priority.id,
"issue_type": issue_type.id,
"reporter": reporter.id,
}
When I try to create a new Issue, the "new Issue" form is displayed normally, but when I save the form I get a Django error with a stack trace I don't understand because it does not have a reference to any of my code, so I have no idea where to start debugging.
16:22:48 ERROR Internal Server Error: /ui/issue/create
Traceback (most recent call last):
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/views/generic/base.py", line 103, in view
return self.dispatch(request, *args, **kwargs)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/contrib/auth/mixins.py", line 73, in dispatch
return super().dispatch(request, *args, **kwargs)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/contrib/auth/mixins.py", line 109, in dispatch
return super().dispatch(request, *args, **kwargs)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/views/generic/base.py", line 142, in dispatch
return handler(request, *args, **kwargs)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/views/generic/edit.py", line 184, in post
return super().post(request, *args, **kwargs)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/views/generic/edit.py", line 152, in post
if form.is_valid():
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/forms/forms.py", line 205, in is_valid
return self.is_bound and not self.errors
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/forms/forms.py", line 200, in errors
self.full_clean()
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/forms/forms.py", line 439, in full_clean
self._post_clean()
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/forms/models.py", line 485, in _post_clean
self.instance = construct_instance(
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/forms/models.py", line 82, in construct_instance
f.save_form_data(instance, cleaned_data[f.name])
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/db/models/fields/__init__.py", line 1006, in save_form_data
setattr(instance, self.name, data)
File "/Users/a0r470/git/issue_tracker/env/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 237, in __set__
raise ValueError(
ValueError: Cannot assign "'2'": "Issues.reporter" must be a "User" instance.
[27/Dec/2022 16:22:48] "POST /ui/issue/create HTTP/1.1" 500 120153
Generally I understand that under the covers, Django creates two fields in the Issues model for me:
reporter
reporter_id
and I understand that the reporter field needs to contain a User instance instead of an integer (2). BUT I don't know WHERE in my code I should do this assignment.
I have tried overriding a few methods in my CreateUpdateIssueForm and IssueCreateView as a way to try to find where my code is causing problems - no luck so far.
In my IssueCreateView(generic.CreateView) class, I added the following to my form_valid() method, intending to retrieve the correct User record and assign it to form.instance.reporter, but the code appears to be failing before it gets to my form_valid() method.
def form_valid(self, form):
User = get_user_model()
if "reporter" in self.kwargs:
form.instance.reporter = User.objects.get(id__exact=self.kwargs["reporter"])
Clearly I do not fully understand the flow of control in these Generic View classes.
Thank you for any help you can provide!
I discovered that trying to migrate my own Users model to a CustomUser model is a non-trivial undertaking! I learned this from Will Vincent and his excellent post on this very topic!
Django Best Practices: Custom User Model
The Django documentation also states that migrating to the Django User in the midst of an existing project is non-trivial.
Changing to a custom user model mid-project
So, to solve my problem I started with a new empty project with only the CustomUser in my models.py as Mr. Vincent described, which worked perfectly.
After that, I setup the rest of my model classes in models.py, referencing the CustomUser model as needed.
assignee = models.ForeignKey(
to=CustomUser,
on_delete=models.RESTRICT,
blank=True,
null=True,
)
And copied the rest of my template files, view source files, static files, etc. from my original project into this new project.
My codebase is now working as expected using the Django User model.
Huge Thanks to Mr. Will Vincent's excellent article on this issue!
'm building a small webstore , in the product page i put the order form using FormMixin and TemplateView, when i submit the order i get a " null value in column "customer_id" of relation "products_order" violates not-null constraint
DETAIL: Failing row contains (21, null)." error :
models.py
class Product(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
nominal_price = models.PositiveIntegerField(verbose_name='prix normal',)
reduced_price = models.PositiveIntegerField(blank=True, null=True)
quantity = models.PositiveIntegerField(default=10)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
photo = models.ImageField(upload_to="img/products/", default="img/products/user_default.png")
def __str__(self):
return self.name
class Customer(models.Model):
full_name = models.CharField(max_length=150)
address = models.CharField(max_length=1500, null=True)
phone = models.IntegerField()
city = models.CharField(max_length=100)
email = models.EmailField(null=True)
def __str__(self):
return self.full_name
class Order (models.Model):
product = models.ManyToManyField(Product, through='OrderProduct')
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
class OrderProduct(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
views.py :
class ProductDetailView(FormMixin, TemplateView):
model = Product
template_name = 'product.html'
form_class = OrderForm
def get_success_url(self):
return reverse('index')
def post(self, request, *args, **kwargs):
context = self.get_context_data()
form = OrderForm(request.POST)
if context['form'].is_valid():
product = get_object_or_404(Product, name=self.kwargs['product_name'])
customer = form.save()
instance = Order.objects.create(customer=customer)
instance.product.set(product)
return super(TemplateView, self)
def get_context_data(self, **kwargs):
context = super(ProductDetailView, self).get_context_data(**kwargs)
context['product'] = Product.objects.get(name=self.kwargs['product_name'])
context['form'] = self.get_form()
return context
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.ProductListView.as_view(), name='index'),
path ('<str:product_name>/', views.ProductDetailView.as_view(), name='product'),
path('categories/', views.CategoryListView.as_view(), name='categories'),
path('add_category', views.AddCategoryView.as_view(), name='add_category'),
path('add_product/', views.AddProductView.as_view(), name='add_product'),
path('categories/<str:category_name>/', views.CategoryProductsView.as_view(), name='category_products'),
]
Traceback
Traceback (most recent call last):
File "D:\Python\Django\Django projects\Store\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "D:\Python\Django\Django projects\Store\venv\lib\site-packages\django\utils\deprecation.py", line 119, in __call__
response = self.process_response(request, response)
File "D:\Python\Django\Django projects\Store\venv\lib\site-packages\django\middleware\clickjacking.py", line 26, in process_response
if response.get('X-Frame-Options') is not None:
AttributeError: 'super' object has no attribute 'get'
I am trying to add a set of inline forms, but when I try to save the information of the forms it throws me this error
views.py
The intention is to create more orders for a particular client, for that reason I focus on the "createOrder" function to achieve it.
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .models import *
from django.forms import inlineformset_factory
from .forms import OrderForm, CustomerForm
# Create your views here.
def home(request):
orders_value = Order.objects.all()
customer_value = Customer.objects.all()
total_orders_value = orders_value.count()
total_customers_value = customer_value.count()
pending_value = orders_value.filter(status='Pending').count()
delivered_value = orders_value.filter(status='Delivered').count()
context = {'orders_key': orders_value, 'customer_key': customer_value,
'total_orders_key':total_orders_value, 'pending_key': pending_value,
'delivered_key': delivered_value}
return render (request, 'accounts/dashboard.html', context)
def products(request):
products_value = Product.objects.all()
return render (request, 'accounts/products.html', {'products_key': products_value})
def customer(request, pk_test):
customer_value = Customer.objects.get(id=pk_test)
orders_value = customer_value.order_set.all()
orders_value_count = orders_value.count()
context = {'customer_key':customer_value, 'orders_key': orders_value, 'orders_key_count': orders_value_count}
return render (request, 'accounts/customer.html', context)
def createOrder(request, pk):
OrderFormSet= inlineformset_factory(Customer, Order, fields=('product', 'status'))
customer = Customer.objects.get(id=pk)
form_set_value= OrderFormSet(instance=customer)
if request.method == 'POST':
form_set_value= OrderFormSet(request, instance=customer)
if form_set_value.is_valid:
form_set_value.save()
return redirect('/')
context = {'form_set_key':form_set_value}
return render(request, 'accounts/order_form.html', context)
order_form.html
{% extends 'accounts/main.html' %}
{% load static %}
{% block content %}
<form action="" method="POST">
{% csrf_token %}
{{ form_set_key.management_form }}
{% for form in form_set_key %}
{{form}}
<hr>
{% endfor %}
<input type="submit" name="Submit">
</form>
{% endblock %}
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name="home"),
path('products/', views.products, name="products"),
path('customer/<int:pk_test>/', views.customer, name="customer"),
path('create_order/<int:pk>', views.createOrder, name='create_order'),
path('update_order/<int:pk>', views.updateOrder, name='update_order'),
path('delete_order/<int:pk>', views.deleteOrder, name='delete_order'),
path('create_customer/', views.createCustomer, name='create_customer'),
]
forms.py
from django.forms import ModelForm, fields
from .models import Order, Customer
class OrderForm(ModelForm):
class Meta:
model = Order
# fields = ['customer', 'product']
fields = '__all__'
class CustomerForm(ModelForm):
class Meta:
model = Customer
fields = '__all__'
models.py
from django.db import models
from django.db.models.deletion import SET_NULL
class Customer(models.Model):
name = models.CharField(max_length=200, null=True)
phone = models.CharField(max_length=200, null=True)
email = models.CharField(max_length=200, null=True)
data_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField(max_length=200, null=True)
def __str__(self):
return self.name
class Product(models.Model):
CATEGORY = (
('Indoor', 'Indoor'),
('Out Door', 'Out Door'),
)
name = models.CharField(max_length=200, null=True)
prince = models.FloatField(null=True)
category = models.CharField(max_length=200, null=True, choices=CATEGORY)
description = models.CharField(max_length=200, null=True, blank=True)
data_created = models.DateTimeField(auto_now_add=True, null=True)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.name
class Order(models.Model):
STATUS = (
('Pending', 'Pending'),
('Out for delivery', 'Out for delivery'),
('Delivered', 'Delivered'),
)
customer = models.ForeignKey(Customer, null=True, on_delete=SET_NULL)
product = models.ForeignKey(Product, null=True, on_delete=SET_NULL)
status = models.CharField(max_length=200, null=True, choices=STATUS)
data_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.product.name
Traceback
Traceback (most recent call last):
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\le\Desktop\django-course\Django(02-09-21)\crm1\accounts\views.py", line 47, in createOrder
form_set_value.save()
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\models.py", line 681, in save
return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\models.py", line 787, in save_existing_objects
if not self.initial_forms:
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\formsets.py", line 200, in initial_forms
return self.forms[:self.initial_form_count()]
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\utils\functional.py", line 48, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\formsets.py", line 157, in forms
for i in range(self.total_form_count())
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\formsets.py", line 130, in total_form_count
return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\utils\functional.py", line 48, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\formsets.py", line 113, in management_form
form.full_clean()
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\forms.py", line 372, in full_clean
self._clean_fields()
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\forms.py", line 384, in _clean_fields
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
File "C:\Users\le\anaconda3\envs\myenv\lib\site-packages\django\forms\widgets.py", line 263, in value_from_datadict
return data.get(name)
Exception Type: AttributeError at /create_order/1
Exception Value: 'WSGIRequest' object has no attribute 'get'
I think it has to do with the way the information is managed from the template, but I'm not really sure. Can someone help me?
Change:
OrderFormSet(request, instance=customer)
to:
OrderFormSet(request.POST, instance=customer)
The formset requires the post data, not the request object.
I am trying to follow this http://www.django-rest-framework.org/api-guide/fields/#custom-fields to serialize a Point in GeoDjango. But only get the exception below. Can't figure out where my mistake is.
It outputs correctly, but get an error while inserting the same output.
Error output:
Got a `TypeError` when calling `Venue.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `Venue.objects.create()`. You may need to make the field read-only, or override the VenueSerializer.create() method to handle this correctly.
Original exception was:
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py", line 916, in create
instance = ModelClass.objects.create(**validated_data)
File "/usr/local/lib/python3.5/dist-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 394, in create
obj.save(force_insert=True, using=self.db)
TypeError: save() got an unexpected keyword argument 'force_insert'
From serializers.py:
from django.contrib.gis.geos import Point
class LocationField(serializers.Field):
def to_representation(self, obj):
return "%s,%s" % (obj.y, obj.x)
def to_internal_value(self, data):
lat,lng = [str(col) for col in data.split(',')]
pnt = Point(float(lat), float(lng), srid=4326)
return pnt
class VenueSerializer(serializers.ModelSerializer):
location = LocationField()
class Meta:
model = Venue
fields = ('id', 'name', 'description', 'website', 'location')
read_only_fields = ('created_at', 'modified_at')
From models.py:
from __future__ import unicode_literals
from django.contrib.gis.db import models
from django.conf import settings
from django.contrib.auth import get_user_model
from pygeocoder import Geocoder
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='system')[0]
class Venue(models.Model):
"""
Model for venues
"""
name = models.CharField(max_length=254)
description = models.TextField(max_length=512, blank=True)
website = models.CharField(max_length=254, blank=True)
address = models.CharField(max_length=254, blank=True)
location = models.PointField()
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
objects = models.GeoManager()
def __str__(self):
return self.name
class Meta:
ordering = ('created_at',)
def save(self):
if not self.address:
geo = Geocoder.reverse_geocode(self.location.y, self.location.x)
self.address = geo.formatted_address
super(Venue, self).save()
The save() function in models.py was missing the **kwargs
Corrected to the one below:
def save(self, **kwargs):
if not self.address:
geo = Geocoder.reverse_geocode(self.location.y, self.location.x)
self.address = geo.formatted_address
super(Venue, self).save()
Here is the model that I am testing:
class Entity(TimestampModerated):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
description = models.TextField(verbose_name='description of entity', blank=True)
media = models.URLField(verbose_name='media representing entity (image, mp3, mpg, etc)',
blank=True, null=True)
uuid = models.UUIDField(db_index=True,
default=uuid_lib.uuid4(),
editable=False,
)
owner = models.ForeignKey('auth.User', related_name='entities', on_delete=models.CASCADE)
tags = TaggableManager(blank=True)
def get_absolute_url(self):
return reverse('entities:detail', kwargs={'slug': self.slug})
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "entities"
Here is the ViewSet:
class EntityViewSet(viewsets.ModelViewSet):
"""
This viewsetomatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Entity.objects.all()
serializer_class = EntitySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
lookup_field = 'slug'
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Here are my urls:
router = DefaultRouter()
router.register(r'entities', views.EntityViewSet)
router.register(r'users', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
Here is my test:
class EntitiesAPITests(test.APITestCase):
def setUp(self):
test_user = models.User.objects.create(username='testuser', password='password')
def test_login_post(self):
client = APIClient()
client.login(username='testuser', password='password')
response = client.post('/api/entities/', data={
'name': 'Sean Penn',
'slug': 'sean-penn'
})
self.assertContains(response, 200)
client.logout()
When I run the tests I get the following error, which I haven't been able to figure out:
======================================================================
FAIL: test_login_post (entities.tests.test_api.EntitiesAPITests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/xxx/xxx/entities/tests/test_api.py", line 63, in test_login_post
self.assertContains(response, 200)
File "/Users/xxx/xxx/.virtualenv/lib/python3.6/site-packages/django/test/testcases.py", line 385, in assertContains
response, text, status_code, msg_prefix, html)
File "/Users/xxx/xxx/.virtualenv/lib/python3.6/site-packages/django/test/testcases.py", line 357, in _assert_contains
" (expected %d)" % (response.status_code, status_code)
AssertionError: 400 != 200 : Couldn't retrieve content: Response code was 400 (expected 200)
----------------------------------------------------------------------
Ran 10 tests in 0.280s
FAILED (failures=1)
Destroying test database for alias 'default'...
Here is the Serializer:
from django.contrib.auth.models import User
from rest_framework import serializers
from taggit_serializer.serializers import TagListSerializerField
from entities.models import Entity
class EntitySerializer(serializers.ModelSerializer):
owner = serializers.CharField(source='owner.username', read_only=True)
tags = TagListSerializerField()
class Meta:
model = Entity
fields = ('id', 'created', 'name', 'description', 'media', 'moderation_code', 'owner', 'updated', 'tags',
'slug', 'uuid')
Try changing like this,
class EntitiesAPITests(test.APITestCase):
def setUp(self):
self.user = models.User.objects.create(username='testuser', password='password')
def test_login_post(self):
client = APIClient()
client.force_authenticate(self.user)
response = client.post('/api/entities/', data={
'name': 'Sean Penn',
'slug': 'sean-penn'
})
self.assertContains(response, 200)
client.logout()