Cant upload multilple images using django admin - django

I have two models: one is the post model that can have multiple linked images. The image model has a foreign key to the post. That is, the images have a foreign key for a single post. When adding a new post I want to upload multiple images at the same time. Here is the code:
model.py
class Post(models.Model):
title = models.CharField(max_length=255)
thumbnail = models.ImageField(upload_to='thumbnails')
summary = RichTextField()
body = RichTextUploadingField()
created_at = models.DateField(auto_now_add=True)
class Meta:
ordering = ['-created_at',]
def __str__(self):
return self.title
class Imagens(models.Model):
img = models.ImageField(
upload_to = "media/",
)
post = models.ForeignKey(
"Post", on_delete=models.CASCADE, default=1)
admin.py
class ImageAdminForm(forms.ModelForm):
class Meta:
model = Imagens
fields = ['img',]
def __init__(self, *args, **kwargs):
super(ImageAdminForm, self).__init__(*args, **kwargs)
self.fields['img'] = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class ImageInline(admin.TabularInline):
model = Imagens
extra = 1
form = ImageAdminForm
class PostAdmin(admin.ModelAdmin):
search_fields = ['title']
inlines = [ImageInline,]
def save_model(self, request, obj, form, change):
obj.save()
files = request.FILES.getlist('img') #he gets nothing
print(files)
for f in files:
x = Imagens.objects.create(post=obj,img=f)
x.save()
admin.site.register(Post, PostAdmin)
The problem is that if I save the post object it saves only one image and if I try to get the list of images it gives an empty list. Sorry I'm new to Django and even python. Every help is welcome.

Well, that's just a workaround. What I did was make the main model share the form with the inlined model. So, on the change page the files appear and I'm not obliged to post the images either, but it's full of fields.
class ImageAdminForm(forms.ModelForm):
imgens = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
def __init__(self, *args, **kwargs):
super(ImageAdminForm, self).__init__(*args, **kwargs)
self.fields['imgens'].required = False
self.fields['img'] = forms.ImageField()
self.fields['img'].required = False
class ImageInline(admin.TabularInline):
model = Imagens
extra = 1
form = ImageAdminForm
class PostAdmin(admin.ModelAdmin):
search_fields = ['title']
inlines = [ImageInline]
form = ImageAdminForm
def save_model(self, request, obj, form, change):
obj.save()
files = request.FILES.getlist('imgens')
for f in files:
x = Imagens.objects.create(post=obj,img=f)
x.save()
admin.site.register(Post, PostAdmin)

Related

How to upload multiple images with django rest framework CreateAPI in view and create method in serializers

I have a post serializer and a postimage serializer to upload multiple images to a post. I have this serializer, but I am not sure how to make it in a way, so that I can upload multiple images, for example 5 images with a single post, like how we use with formsets coz now I can upload only 1 image in 'images' field.
These are the serializers. Please do have a look and let me know what changes I have to make...
Thanks
class PostImageSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = ['id', 'images',]
class PostSerializer(TaggitSerializer, serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
post_date = serializers.ReadOnlyField()
postimage_set = PostImageSerializer(many=True)
likes = UserSerializer(many=True)
class Meta:
model = Post
fields = ['id','title', 'post_date', 'updated', 'user', 'image', 'postimage_set']
def create(self,validated_data):
user = self.context['request'].user
title = validated_data['title']
image = self.context['request'].FILES.get('image')
images = self.context['request'].FILES.get('images')
m1 = Post(user=user,title=title,image=image,)
m1.save()
m2 = PostImage(post=m1, images= images)
m2.save()
validated_data['images']=m2.images
validated_data['image']=m1.image
return validated_data
views
class CreatePostAPIView(generics.CreateAPIView):
serializer_class = PostCreateSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
serializer = PostCreateSerializer(data=request.data, context={'request':request,})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This solved the problem
def create(self, validated_date):
images = self.context['request'].FILES.getlist('images')
for image in list(images):
m2 = PostImage(post=m1, images= image)
m2.save()
This is how you do the multiple upload on drf: example below on create you can access the multiple item you posted:
e.g posted:
Formdata
images: File.jpeg
images: File2.jpeg
images: File3.jpeg
class TestSerializer(serializers.Serializer):
images = serializers.ListField(child=serializers.FileField())
class CreatePostAPIView(generics.CreateAPIView):
permission_classes = [AllowAny]
serializer_class = TestSerializer
def create(self, request, *args, **kwargs):
images = request.data['images']
I would do it like this.
Make a PostModel and a PostImageModel (with a post ForeignKey) for each post image. And then in the serializer, make relation field with both.
Something like this:
models.py
class Post(Model):
post details ...
class PostImage(Model):
post = models.ForeigKey(Post, ...)
image = models.ImageField(...)
serializers.py
class PostImageSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = '__all__'
class PostSerializer(serialiers.ModelSerializer):
images = serializers.SlugRelatedField(slug_field='image', many=True, ...)
class Meta:
model = Post
fields = [..., 'images']
views.py
class CreatePostView(CreateView):
classForm = PostForm (with images field)
def form_valid(self, form):
new_post = form.save(commit=False)
for image in new_post.images:
new_image = PostImage(post=new_post, image=image)
new_image.save()
new_post.save()
return super()form_valid(form)
So then in your api you should be able to see the post model with the images attached to it.

Need help getting correct instance for form_valid in a generic view

I can't work out how to get the correct instance for the form_valid part of my generic view.
I am trying to allow a user to post on their project wall(bit like Facebook). I need the post to be related to an individual project(a user can have more than one project). Should the instance be a pk or the project title? Any example code or help would be very appreciated! I struggle understanding how when you create a new post, it knows which project to associate itself with.
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
newpost = form.save(commit=False)
form.instance.user = self.request.user
newpost.save()
self.object = newpost
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
models
class UserProject(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
project_overview = models.CharField(max_length=1000)
project_picture = models.ImageField(upload_to='project_images', blank=True)
date_created = models.DateTimeField(auto_now_add=True)
project_views = models.IntegerField(default=0)
project_likes = models.IntegerField(default=0)
project_followers = models.IntegerField(default=0)
slug = models.SlugField(max_length=100, unique=True) #should this be unique or not?
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(UserProject, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
forms
#form to add project details
class UserProjectForm(forms.ModelForm):
class Meta:
model = UserProject
fields = ('title', 'project_picture', 'project_overview')
#form to create a post
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
Ok, in that case, I would recommend a URL something like
url(r'^(?P<pk>\d+)/post/add/$', views.NewPostCreateView.as_view(), name='...'),
and then a view like
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
# Find project by using the 'pk' in the URL
project = get_object_or_404(UserProject, pk=self.kwargs['pk'])
# Then just set the project on the newPost and save()
self.object.project = project
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
# Unchanged ...
I see in your code that you were trying to do something with the user but I don't understand why your Post does not have a user field (you may want to add a created_by) and the UserProject should already have a user set.
I am also assuming the user got to the his/her project first, so you know by definition that the project he is adding a post to is his. If that is not the case, then just change the logic to get the UserProject through a regular query. e.g. maybe with `UserProject.objects.get(user = self.request.user) if there is one project per user (again, just as an example).
Anyway, I am making some assumptions here, but hopefully the main question was how to set the project on the newPost and that is answered in my example.

Elegantly set a single attribute of a Model used in a OneToOneField using a ModelForm

I am able to update the height attribute of MyLittleModel using MyModelForm(OneToOneField(MyLittleModel)) as follows:
models.py
class MyLittleModel(models.Model):
height = models.IntegerField()
has_color = models.NullBooleanField(null=True, blank=True)
class MyModel(models.Model):
my_little_model = models.OneToOneField(MyLittleModel)
age = models.IntegerField()
is_male = models.BooleanField(default=False)
forms.py
class MyModelForm(forms.ModelForm):
height = forms.IntegerField(max_length=30)
class Meta:
model = MyModel
fields = ("height",
"age")
views.py
class MyUpdateView(UpdateView):
form_class = MyModelForm
model = MyModel
template_name = 'my_template.html'
def form_valid(self, form):
my_little_model = MyLittleModel.objects.create(form.cleaned_data["height"])
form.instance.my_little_model = my_little_model
form.instance.save()
return super(MyUpdateView, self).form_valid(form)
def get_success_url(self):
return reverse("my_list_view")
urls.py
urlpatterns = patterns('',
url(regex=r'^update/(?P<pk>\d+)/$', view=MyUpdateView.as_view(), name="my_update_view"),
)
I think this is not a good coding style because this forces you to modify your code in the ModelForm as well as in the View, it would be preferable that this would happen in only one location.
So is it possible to set a value to my_little_model.height without modifying my views code as I did?
Note: I don't like the title to this question, if someone has a suggestion for renaming it into something more readable please let me know.
I would do this by overriding your MyModelForm's save method, like so:
class MyModelForm(forms.ModelForm):
height = forms.CharField(max_length=30)
class Meta:
model = MyModel
fields = ("height",
"age")
def save(self, *args, **kwargs):
my_little_model = MyLittleModel.objects.create(height=self.cleaned_data["height"])
self.instance.my_little_model = my_little_model
self.instance.save()
return super(MyModelForm2, self).save(*args, **kwargs)
Then your view becomes:
class MyUpdateView(UpdateView):
template_name = "my_template.html"
form_class = MyModelForm
model = MyModel
def get_success_url(self):
return reverse("my_list_view")
This way, all your logic for updating the model information lives in the form - if you want to change what's being updated, you can do it all in one place.

Django: Setting current user on a model to use in InlineModelAdmin

I have some models like that:
class BaseModel(models.Model):
created_by = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_created")
created_date = models.DateTimeField(_('Added date'), auto_now_add=True)
last_updated_by = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_updated")
last_updated_date = models.DateTimeField(_('Last update date'), auto_now=True)
class Meta:
abstract = True
class Image(BaseModel):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
name = models.CharField(_('Item name'), max_length=200, blank=True)
image = models.ImageField(_('Image'), upload_to=get_upload_path)
def save(self, *args, **kwargs):
if self.image and not GALLERY_ORIGINAL_IMAGESIZE == 0:
width, height = GALLERY_ORIGINAL_IMAGESIZE.split('x')
super(Image, self).save(*args, **kwargs)
filename = os.path.join( settings.MEDIA_ROOT, self.image.name )
image = PILImage.open(filename)
image.thumbnail((int(width), int(height)), PILImage.ANTIALIAS)
image.save(filename)
super(Image, self).save(*args, **kwargs)
class Album(BaseModel):
name = models.CharField(_('Album Name'), max_length=200)
description = models.TextField(_('Description'), blank=True)
slug = models.SlugField(_('Slug'), max_length=200, blank=True)
status = models.SmallIntegerField(_('Status'),choices=ALBUM_STATUSES)
images = generic.GenericRelation(Image)
I use BaseModel abstract model for my all models to track save and update logs. I can use ModelAdmin class to set user fields automatically:
class BaseAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.last_updated_by = request.user
obj.save()
class AlbumAdmin(BaseAdmin):
prepopulated_fields = {"slug": ("name",)}
list_display = ('id','name')
ordering = ('id',)
That works. All BaseAdmin fields are filled automatically. But I want to add Images to Albums by Inline. So, I change my admin.py like that:
from django.contrib.contenttypes import generic
class ImageInline(generic.GenericTabularInline):
model = Image
extra = 1
class AlbumAdmin(BaseAdmin):
prepopulated_fields = {"slug": ("name",)}
list_display = ('id','name')
ordering = ('id',)
inlines = [ImageInline,]
When I save page, I get an error: gallery_image.created_by_id may not be NULL on first super(Image, self).save(*args, **kwargs) row of Image model save method. I know it's because of GenericTabularInline class doesn't have a "save_model" method to override.
So, the question is, how can I override save method and set current user on InlineModelAdmin classes?
I have found a solution on another question: https://stackoverflow.com/a/3569038/198062
So, I changed my BaseAdmin model class like that, and it worked like a charm:
from models import BaseModel
class BaseAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.last_updated_by = request.user
obj.save()
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
if isinstance(instance, BaseModel): #Check if it is the correct type of inline
if not instance.created_by_id:
instance.created_by = request.user
instance.last_updated_by = request.user
instance.save()
Note that, you must extend same abstract class for the ModelAdmin that contains the inlines to use this solution. Or you can add that save_formset method to ModelAdmin that contains the inline specifically.
I wanted the user to be set on all my models no matter where/how they were manipulated. It took me forever to figure it out, but here's how to set it on any model using middleware:
"""Add user created_by and modified_by foreign key refs to any model automatically.
Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry
class WhodidMiddleware(object):
def process_request(self, request):
if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if hasattr(request, 'user') and request.user.is_authenticated():
user = request.user
else:
user = None
mark_whodid = curry(self.mark_whodid, user)
signals.pre_save.connect(mark_whodid, dispatch_uid = (self.__class__, request,), weak = False)
def process_response(self, request, response):
signals.pre_save.disconnect(dispatch_uid = (self.__class__, request,))
return response
def mark_whodid(self, user, sender, instance, **kwargs):
if instance.has_attr('created_by') and not instance.created_by:
instance.created_by = user
if instance.has_attr('modified_by'):
instance.modified_by = user
In addition to mindlace's answer; when the created_by field happens to have null=True the not instance.created_by gives an error. I use instance.created_by_id is None to avoid this.
(I'd rather have posted this as a comment to the answer, but my current reputation doesn't allow...)

Dynamic Form fields in `__init__` in Django admin

I want to be able to add fields to django admin form at runtime. My model and form:
#admin.py
class SitesForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SitesForm, self).__init__(*args, **kwargs)
self.fields['mynewfield'] = forms.CharField()
class SitesAdmin(admin.ModelAdmin):
form = SitesForm
admin.site.register(Sites,SitesAdmin)
#model.py
class Sites(models.Model):
url = models.URLField(u'URL')
is_active = models.BooleanField(default=True, blank=True)
is_new = models.BooleanField(default=False, blank=True)
group = models.ForeignKey('SitesGroup')
config = models.TextField(blank=True)
Field mynewfield isn't displayed in form. Why?
You shouldn't be adding a new field to your form in that way, you can just do it as you would any other field and the form will contain both the Model's original fields and your new fields:
class SitesForm(forms.ModelForm):
mynewfield = forms.CharField(max_length=255, blank=True)
class Meta:
model = Sites
class SitesAdmin(admin.ModelAdmin):
form = SitesForm
admin.site.register(Sites, SitesAdmin)
Edit: Sorry, should have read what you had written a little better. If you want a dynamic field like that, then you need to do the following and it will do exactly what you want:
class SitesForm(forms.ModelForm):
class Meta:
model = Sites
def __init__(self, *args, **kwargs):
self.base_fields['mynewfield'] = forms.CharField(max_length=255, blank=True)
super(SitesForm, self).__init__(*args, **kwargs)
class SitesAdmin(admin.ModelAdmin):
form = SitesForm
admin.site.register(Sites, SitesAdmin)
It's the base_fields that gets composed by the metaclass that holds the fields that the form will use.
Solution:
class AdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AdminForm, self).__init__(*args, **kwargs)
self.fields.insert(1, 'myfield', forms.CharField())
class MyAdmin(admin.ModelAdmin):
form = AdminForm
def get_fieldsets(self, request, obj=None):
return (
(None, {
'fields': (..., 'myfield',),
}),
)