I'm trying to make a custom form field in Django that allows a user to link a file or upload a file. To do this, I'm creating a subclass of the MultiValueField with the fields property set to (URLField(), FileField()). I'm not sure this is the right approach, but I keep getting an error I can't understand:
'MyFileField' object has no attribute 'attrs'
Here is my code. Can anyone explain what's going on?
from django import forms
from django.core.exceptions import ValidationError
from .models import Case, Person, Opp, Category
class MyFileField(forms.MultiValueField):
def compress(self, data_list):
# I'll get to this once the attr error goes away
pass
def __init__(self, *args, **kwargs):
fields = (
forms.URLField(), forms.FileField()
)
super(MyFileField, self).__init__(fields=fields, *args, **kwargs)
class CaseForm(forms.ModelForm):
class Meta:
model = Case
fields = ['title', 'file', 'categories']
widgets = {
'file': MyFileField
}
The problem is that you are calling init on an abstract class.
super(MyFileField, self).__init__(fields=fields, *args, **kwargs)
But the base class is abstract.
Look at https://docs.djangoproject.com/en/1.8/ref/forms/fields/#multivaluefield
and
Python's super(), abstract base classes, and NotImplementedError
Related
I am working on Django React Project using the Django REST FRAMEWORK,I am trying to post some data tied to my model.
The list view and the detail view of the project works pretty fine,The only problem is when I try to make a POST request.
Whenever I Try post the data in the CreateAPIView I get an error :
Got a `TypeError` when calling `Article.objects.create()`. This may be because
you have a writable field on the serializer class that is not a valid argument to
`Article.objects.create()`. You may need to make the field read-only, or override
the ArticleSerializer.create() method to handle this correctly.
I have searched through various past problems but non of them seem to fix my problem.
Here is my serializers file:
from rest_framework import serializers
from articles.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('id','title','content','star_count','like_count','comment_count','avatar')
Here is my views file
from rest_framework.generics import ListAPIView,RetrieveAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView
from .serializers import ArticleSerializer
from articles.models import Article
from rest_framework import viewsets
class ArticleViewSets(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
models file
content = models.TextField()
comment_count = models.IntegerField(default=0,null=True,blank=True)
like_count = models.IntegerField(default=0,null=True,blank=True)
star_count = models.IntegerField(default=0,null=True,blank=True)
avatar = models.ImageField(null=True,blank=True)
def __str__(self):
return self.title
def save(self):
if not self.slug:
self.slug = slugify(self.title)
super(Article,self).save()
Here is the error generated when I try to make a POST request based on the django rest framework createAPIVew
Got a `TypeError` when calling `Article.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `Article.objects.create()`. You may need to make the field read-only, or override the ArticleSerializer.create() method to handle this correctly.
I've got a Django Rest Framework ModelViewSet and am trying to use the TemplateHTMLRenderer to display HTML. Following along in the tutorial:
from rest_framework import permissions, renderers, viewsets
from rest_framework.decorators import link
from . import models, serializers
from .permissions import IsOwnerOrReadOnly
class SnippetViewSet(viewsets.ModelViewSet):
template_name = 'snippet-list.html'
queryset = models.Snippet.objects.all()
serializer_class = serializers.SnippetSerializer
renderer_classes = (renderers.TemplateHTMLRenderer,)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
#link(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def pre_save(self, obj):
obj.owner = self.request.user
If I add a key in def resolve_context() I can access the model objects in my template that are passed into the RequestContext. If I don't add the data key then I don't know how to access the Snippets.
def resolve_context(self, data, request, response):
if response.exception:
data['status_code'] = response.status_code
#return RequestContext(request, data) # original source on github
return RequestContext(request, {'data': data}) # if I add a key I can access it
So I've got to be missing something easy or how I'm expecting this to behave is not how the authors intended?
I would go this way:
class SnippetViewSet(viewsets.ModelViewSet):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
renderer_classes = (renderers.JSONRenderer, renderers.TemplateHTMLRenderer)
def list(self, request, *args, **kwargs):
response = super(SnippetViewSet, self).list(request, *args, **kwargs)
if request.accepted_renderer.format == 'html':
return Response({'data': response.data}, template_name='home.html')
return response
and use http://127.0.0.1:8000/snippets/.html to get table (or whatever suffix you use).
This way you don't override resolver for each render type.
Other solution would be to just create dedicated view for list action and only use HTML renderer. But then you would have a small code duplication.
I also met the same question with you, and I also thought so. I came here by Google. I didn't like override "def list(self, request, *args, **kwargs):", because I felt it broke the viewset design idea. After I researched the snippet tutorial and source code in the "site-packages\rest_framework", I got the key, not viewset but "serializer.data". In the "site-packages\rest_framework\serializers.py", I found the class BaseSerializer, i.e., the top base class of ModelSerializer. Its property "data" is defined as follows:
#property
def data(self):
... # omitted the function body here, because it didn't care about this solution.
return self._data
This property data is just the "serializer.data" that is just the response passed to template. So I just overrided the data property in "snippets/serializers.py", and after calling the father's method, set the key for the returned data:
class SnippetSerializer(serializers.ModelSerializer):
#property
def data(self):
return { 'data' : super(serializers.ModelSerializer, self).data } #'data' can be replaced with other wanted name.
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
OK, use the name 'data' in your template.
I subclassed and overrode the method that provides the template context, so that the serializer data is available under data within the template context:
from rest_framework.renderers import TemplateHTMLRenderer
class MyHTMLRenderer(TemplateHTMLRenderer):
def get_template_context(self, data, renderer_context):
context = {'data': data}
response = renderer_context['response']
if response.exception:
data['status_code'] = response.status_code
return context
Inside the viewset use renderer class
renderer_classes = (renderers.JSONRenderer, renderers.TemplateHTMLRenderer)
like above and override the ListModelMixin's list method.
mariodev's answer gives the best example also.
In Django i have a model that represents an image with a description that has Foreign Key to another model, this model uses an StdImageField to save the image and auto create a thumbnail. This model also has a choice field with two options representing the type of image.
In my admin i show this model as an inline of the main model, however i'd like to show this model as two separate inlines in the admin as if they were two separate types of objects to the user, for this i'm using 2 proxy models and registering them instead.
The problem is that when i use this proxy models the StdImageField wont resize the uploaded image nor create a thumbnail. I believe this is due to the issue described here
My code is the following (stripped down for the purpose):
models.py
from django.db import models
from stdimage import StdImageField
class MainModel(models.Model):
some_field = models.CharField(max_length = 2)
class SomeModel(models.Model):
SOME_MODEL_TYPE_CHOICES = (
('t1','Type 1'),
('t2','Type 2'),
)
main_model = models.ForeignKey(to='MainModel')
pic = StdImageField(upload_to='img', size =(200,200), thumbnail_size = (100,100))
pic_type = models.CharField(max_length = 2, choices = SOME_MODEL_TYPE_CHOICES)
class SomeModelT1Manager(models.Manager):
def get_query_set(self):
return super(SomeModelT1Manager, self).get_query_set().filter(pic_type='t1')
class SomeModelT1(SomeModel):
objects = SomeModelT1Manager()
class Meta:
proxy = True
def save(self, *args, **kwargs):
if not self.pk:
self.pic_type = 't1'
super(SomeModelT1, self).save(*args,**kwargs)
class SomeModelT2Manager(models.Manager):
def get_query_set(self):
return super(SomeModelT2Manager, self).get_query_set().filter(pic_type = 't2')
class SomeModelT2(SomeModel):
objects = SomeModelT2Manager()
class Meta:
proxy = True
def save(self, *args, **kwargs):
if not self.pk:
self.pic_type = 't2'
super(SomeModelT2, self).save(*args, **kwargs)
admin.py
from django.contrib
import admin
from test_app.models import *
class SomeModelT1Inline(admin.StackedInline):
model = SomeModelT1
exclude = ('pic_type',)
class SomeModelT2Inline(admin.StackedInline):
model = SomeModelT2
exclude = ('pic_type',)
class MainModelAdmin(admin.ModelAdmin):
inlines = [
SomeModelT1Inline,
SomeModelT2Inline
]
admin.site.register(MainModel, MainModelAdmin)
So my question is wheter there is another way of doing this or how do i correct this issue in stdimage. I think the problem could be that contribute_to_class never gets called in StdImageField when in the Proxy context, mainly because __metaclass__ is not set to models.SubfieldBase as explained in the django documentation for custom model fields
However that's only a wild guess as django's FileField or ImageField don't set that either.
Just a guess without any debugging: The StdImageField's contribute_to_class method registers some signal listeners for the orginal SomeModel for post_init and post_save. These handlers don't get called if the sender is a proxy model.
One a bit hackish way around this could be making your own signal receivers for the proxy models that send out the post_save and post_init signals with SomeModel as sender..
EDIT: You could try putting this at the end of your models.py; it's quite a hack and probably cause some errors if you would have to register different handlers for the original model and the proxy models...
from django.db.models.signals import post_save, post_init
from django.dispatch import receiver
#receiver(post_save, sender=SomeModelT1)
def post_save_handler(sender, instance, **kwargs):
post_save.send(sender=SomeModel, instance=instance, **kwargs)
#receiver(post_init, sender=SomeModelT1)
def post_init_handler(sender, instance, **kwargs):
post_init.send(sender=SomeModel, instance=instance, **kwargs)
I want to disable editing ALL objects within a particular TabularInline instance, while still allowing additions and while still allowing editing of the parent model.
I have this trivial setup:
class SuperviseeAdmin(admin.TabularInline):
model = Supervisee
class SupervisorAdmin(admin.ModelAdmin):
inlines = [SuperviseeAdmin]
admin.site.register(Supervisor, SupervisorAdmin)
I have tried adding a has_change_permission function to SuperviseeAdmin that returns False unconditionally, but it had no effect.
I have tried setting actions = None in SuperviseeAdmin but it had no effect.
What might I be overlooking that could get this to work?
User django admin build in function has_change_permission() and return false to restrict object Edit view.
class SuperviseeAdmin(admin.TabularInline):
model = Supervisee
def has_change_permission(self, request):
return False
class SupervisorAdmin(admin.ModelAdmin):
inlines = [SuperviseeAdmin]
admin.site.register(Supervisor, SupervisorAdmin)
See this solution: Django admin: make field editable in add but not edit
Override get_readonly_fields method:
def get_readonly_fields(self, request, obj=None):
if obj: # obj is not None, so this is an edit
return ['name1',..] # Return a list or tuple of readonly fields' names
else: # This is an addition
return []
You can try creating a separate inline class (see the InlineModelAdmin docs) that uses a custom ModelForm where you can customise the the clean method to throw an error when trying to update:
from django.contrib import admin
from django.core.exceptions import ValidationError
from django.forms import ModelForm
from myapp.models import Supervisee
class SuperviseeModelForm(ModelForm):
class Meta(object):
model = Supervisee
# other options ...
def clean(self):
if self.instance.pk:
# instance already exists
raise ValidationError('Update not allowed')
# instance doesn't exist yet, continue
return super(SuperviseeModelForm, self).clean()
class SuperviseeInline(admin.TabularInline):
model = Supervisee
form = SuperviseeModelForm
class SuperviseeAdmin(admin.ModelAdmin):
inlines = [SuperviseeInline]
just make all your fields readonly_fields in your admin.TabularInline as :
class SuperviseeAdmin(admin.TabularInline):
model = Supervisee
readonly_fields = ('your_field', 'your_field2',)
class SuperviseeAdmin(admin.TabularInline):
model = Supervisee
def __init__(self, *args, **kwargs):
super(SuperviseeAdmin, self).__init__(*args, **kwargs)
self.list_display_links = (None, )
I have a booking model that needs to check if the item being booked out is available. I would like to have the logic behind figuring out if the item is available centralised so that no matter where I save the instance this code validates that it can be saved.
At the moment I have this code in a custom save function of my model class:
def save(self):
if self.is_available(): # my custom check availability function
super(MyObj, self).save()
else:
# this is the bit I'm stuck with..
raise forms.ValidationError('Item already booked for those dates')
This works fine - the error is raised if the item is unavailable, and my item is not saved. I can capture the exception from my front end form code, but what about the Django admin site? How can I get my exception to be displayed like any other validation error in the admin site?
In django 1.2, model validation has been added.
You can now add a "clean" method to your models which raise ValidationError exceptions, and it will be called automatically when using the django admin.
The clean() method is called when using the django admin, but NOT called on save().
If you need to use the clean() method outside of the admin, you will need to explicitly call clean() yourself.
http://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects
So your clean method could be something like this:
from django.core.exceptions import ValidationError
class MyModel(models.Model):
def is_available(self):
#do check here
return result
def clean(self):
if not self.is_available():
raise ValidationError('Item already booked for those dates')
I haven't made use of it extensively, but seems like much less code than having to create a ModelForm, and then link that form in the admin.py file for use in django admin.
Pretty old post, but I think "use custom cleaning" is still the accepted answer. But it is not satisfactory. You can do as much pre checking as you want you still may get an exception in Model.save(), and you may want to show a message to the user in a fashion consistent with a form validation error.
The solution I found was to override ModelAdmin.changeform_view(). In this case I'm catching an integrity error generated somewhere down in the SQL driver:
from django.contrib import messages
from django.http import HttpResponseRedirect
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
try:
return super(MyModelAdmin, self).changeform_view(request, object_id, form_url, extra_context)
except IntegrityError as e:
self.message_user(request, e, level=messages.ERROR)
return HttpResponseRedirect(form_url)
The best way is put the validation one field is use the ModelForm... [ forms.py]
class FormProduct(forms.ModelForm):
class Meta:
model = Product
def clean_photo(self):
if self.cleaned_data["photo"] is None:
raise forms.ValidationError(u"You need set some imagem.")
And set the FORM that you create in respective model admin [ admin.py ]
class ProductAdmin(admin.ModelAdmin):
form = FormProduct
I've also tried to solve this and there is my solution- in my case i needed to deny any changes in related_objects if the main_object is locked for editing.
1) custom Exception
class Error(Exception):
"""Base class for errors in this module."""
pass
class EditNotAllowedError(Error):
def __init__(self, msg):
Exception.__init__(self, msg)
2) metaclass with custom save method- all my related_data models will be based on this:
class RelatedModel(models.Model):
main_object = models.ForeignKey("Main")
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.main_object.is_editable():
super(RelatedModel, self).save(*args, **kwargs)
else:
raise EditNotAllowedError, "Closed for editing"
3) metaform - all my related_data admin forms will be based on this (it will ensure that admin interface will inform user without admin interface error):
from django.forms import ModelForm, ValidationError
...
class RelatedModelForm(ModelForm):
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get("main_object")
raise ValidationError("Closed for editing")
super(RelatedModelForm, self).clean() # important- let admin do its work on data!
return cleaned_data
To my mind it is not so much overhead and still pretty straightforward and maintainable.
from django.db import models
from django.core.exceptions import ValidationError
class Post(models.Model):
is_cleaned = False
title = models.CharField(max_length=255)
def clean(self):
self.is_cleaned = True
if something():
raise ValidationError("my error message")
super(Post, self).clean()
def save(self, *args, **kwargs):
if not self.is_cleaned:
self.full_clean()
super(Post, self).save(*args, **kwargs)