Uploading images without ModelForm - python

I am creating a page for adding products to the web shop by the user, and I am having troubles with uploading images.
I have a form with an <input type="file"> and after selecting the files, the files (images) should be temporarily uploaded to the server. After the upload is complete, image preview should be displayed. During the upload, loading spinner GIF with client-side progress indication should be displayed too, along with the option to cancel the upload. Each uploaded image should have an option to be deleted, once uploaded.
Because of all this and other reasons, I'm not using a ModelForm, but creating everything manually.
So, my plan is to attach an onchange listener to that input, and upon files selection, a POST XMLHttpRequest would be sent from JavaScript, which would call a view that would save the images into some kind of table for temporarily uploaded images, and the ID of the row would be something like request.session.session_key, but a little bit different. My issue with this approach is generating that session key, which should be unique and it should last as long as the page is opened. So, refreshing the page would mean generating a new key. request.session.session_key doesn't suit my needs as it remains the same throughout the session. When entire form is submitted, I plan to somehow connect those temporarily uploaded images with my main product instance, and delete them from the table for temporary images.
What do you think of this solution, and if it is viable, how to generate the key?
#EDIT:
models.py:
class Product(models.Model):
class Meta:
abstract = True
category = models.ForeignKey('Category', on_delete=models.CASCADE, blank=False)
title = models.CharField(max_length=200)
price = models.DecimalField(decimal_places=2, max_digits=10)
free_shipping = models.BooleanField()
stock = models.PositiveSmallIntegerField()
image = models.ImageField(upload_to=get_image_path, blank=True, null=True)
date_added = models.DateTimeField(auto_now=True, null=True)
class Book(Product):
year = models.PositiveSmallIntegerField()
publisher = models.CharField(max_length=50)
author = models.CharField(max_length=50)
language = models.CharField(max_length=20)
isbn = models.CharField(max_length=50)
class Shoes(Product):
size = models.PositiveSmallIntegerField()
colour = models.CharField(max_length=20)

I have something similar done using Angular
products.ts
onFileChanged(event) {
let reader = new FileReader();
let me = this;
if (event.target.files.length > 0) {
const file = event.target.files[0];
reader.readAsDataURL(file);
reader.onload = function () {
me.imagen = reader.result;
};
reader.onerror = function (error) {
console.log('Error: ', error);
};
}
}
onSubmit(f){
// f is my form
let postdata=f.value;
postdata.imagen={'imagen': this.imagen};
postdata.usuario=+localStorage.getItem("id_usuario");
postdata.categoria=+postdata.categoria;
postdata.precio=+postdata.precio;
postdata.donacion=+postdata.donacion;
console.log(postdata);
this.articuloServicio.postNuevoArticulo(postdata)
.subscribe(res=>{
this.router.navigateByUrl('/mis_productos')
},(error=>{
alert('Error, verifique la informacion ingresada');
}));
}
models.py
class Image(models.Model):
image=models.ImageField(upload_to='imagenes/',blank=False, null=False)
articule=models.ForeignKey(Articule,on_delete=models.CASCADE,
related_name="images",
null=False, blank=False)
Some images can be for the same product
serializers.py
class ArticuloSerializer(serializers.ModelSerializer):
imagen = ImagenCreateSerializer(write_only=True)
class Meta:
model = Articulo
fields = ('id', 'categoria','usuario','nombre','precio','donacion','descrip', 'imagen')
def create(self, validated_data):
imagenes = validated_data.pop('imagen')
articulo = Articulo(**validated_data)
articulo.save()
for imagen in imagenes.values():
Imagen.objects.create(articulo=articulo, imagen=imagen)
return articulo
views.py
class ArticulosList(generics.ListCreateAPIView):
queryset = Articulo.objects.all()
serializer_class = ArticuloSerializer
def list(self, request, *args, **kwargs):
queryset = Articulo.objects.all()
serializer = ArticuloSerializerDetail(queryset, context={'request': request}, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
serializer = ArticuloSerializer(data=request.data)
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)
In products.ts: In onFileChanged, that manage the file upload, I use Filereader to get the image an is assigned to a
variable in my Angular component. In onSubmit, I get the data from my form f and the image, all the data is located in an object
to be sended in my service in a POST request.
In models.py I share my Image model, that is attached to an Articule, that is other model in my schema. The image field is a
ImageField from Django.
In serializers.py I share my ArticuleSerializer that serialize the data for my Articule get and post views. As you can see
there is a create function, in that you can define how the data is managed when is a post request.
In views.py, I define my Articule view for get and post, the one for post is the create function. To call that view
in your urls.py assign it as
path("endpoint/", ArticuleList.as_view({"get": "list", "post": "create"}))

Related

Django Rest Framework: upload image to a particular existing model's object

Hey I am trying to upload an image using Rest API[via Postman] to an object which already exists, and has all its field populated except for the image. I am using PUT method, to first get the object I want to upload the image to then trying to pass it through the serializer. The images are to be uploaded to my S3 bucket.
The code for my views.py:
#api_view(['PUT'])
#permission_classes([IsAuthenticated])
def putproof(request):
app=MasterTaskHolder.objects.filter(title=request.data['title'],user=request.user)
serializer=ImageUploadSerializer(app,data=request.data,partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response("Posted")
My serializer:
class ImageUploadSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MasterTaskHolder
fields = (
'title','image'
)
My model:
class MasterTaskHolder(models.Model):
status_code = [
('C', 'Completed'),
('P', 'Pending'),
]
title = models.CharField(max_length=50)
point = models.IntegerField()
category = models.CharField(max_length=50, null=True, blank=True)
status = models.CharField(max_length=2, choices=status_code, default='P')
user = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(null=True, blank=True, upload_to="imageproof/")
def __str__(self):
return (f'{self.user} - '+(f'{self.title}'))
I am really new to Django and DRF, any help would be appreciated.
Thank you.
Could you try this?
class ImageUploadSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MasterTaskHolder
fields = (
'title','image'
)
def update(self, instance: MasterTaskHolder, validated_data):
instance.image = validated_data['image']
instance.save()
return instance
In views instead of using serializer.save() use the overrided update that we just create
serializer.update(instance, serializer.validated_data)
Also you are sending a queryset to serializer you need to change that to the object itself and humble suggestion don't forget to validate if that object exist or not.

Django model form render for CreateView takes too long (eventually times out)

I'm using Django generic class views(CreateView) and Django ModelForms. But the form render seems to get stuck for some reason.
Here's my views.py
class NewExternalWorkOrder(CreateView):
model = ExternalWorkOrder
template_name = 'new_external_work_order.html'
form_class = ExternalWorkOrderForm
My ExternalWorkOrder model:
class ExternalWorkOrder(models.Model):
asset = models.ForeignKey(Asset, null=True, blank=True, on_delete=models.DO_NOTHING)
inventory_item = models.ForeignKey(Item, null=True, blank=True, on_delete=models.DO_NOTHING)
inventory_item_quantity = models.PositiveIntegerField(validators=[MinValueValidator(1)], null=True, blank=True)
number = models.CharField(max_length=50, null=True)
date_created = models.DateTimeField(default=timezone.now)
nature_of_problem = models.TextField()
mileage_hours = models.PositiveIntegerField(null=True, blank=True)
#...more fields...
and my form class
class ExternalWorkOrderForm(forms.ModelForm):
def __init__(self, * args, ** kwargs):
super(ExternalWorkOrderForm, self).__init__(*args, **kwargs)
class Meta:
model = ExternalWorkOrder
fields = ['asset', 'inventory_item', 'inventory_item_quantity',
'nature_of_problem', 'mileage_hours']
widgets = {
'asset': forms.Select(attrs={'class': 'browser-default'}),
}
All this was working a few days back but it suddenly stopped working. The page now loads forever and the gunicorn worker nearly always times out(I increased the timeout to over 10 minutes). Sometimes it eventually renders.
I had initially thought that it's the form instantiation but after a few more tests, The instantiation seems to complete but the form wouldn't render.
I have updated my views.py to override the get method as
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
context = {'form': form}
print("About to render with form...")
return render(request, self.template_name, {'form': form})
If I get rid of the form in context, the page renders successfully.
Does anyone have an idea of what might be wrong?
I would suggest that you check the number of foreign model instances choices in your foreign key fields (asset and inventory_item).
If they are too much of them, the page can take a really long time to render.
If this is the case, the solution is to implement a lazy loading of model instances in the select fields of your HTML. (for example, using Select2 AJAX capabilities).

Save the data of current logged user in django

I am a newbie in django and I have a question about how I can save and show only the data of logged user - since my application is multi-tenant.
my view
class ProjetoCreate(CreateView):
model = Projeto
fields = ['nomeProjeto',
'descricao',
'dtInicio',
'deadline',
'nomeSprint',
'status',
]
def get_queryset(self):
logged_user = self.request.user
return Projeto.objects.filter(User=logged_user)
class ProjetoList(ListView):
paginate_by = 2
model = Projeto
my model
class Projeto(models.Model):
nomeProjeto = models.CharField(max_length=20)
descricao = HTMLField()
dtInicio = models.DateField(auto_now=False, auto_now_add=False)
deadline = models.DateField(auto_now=False, auto_now_add=False)
nomeSprint = models.CharField(max_length=30)
status = models.CharField(max_length=20)
Thank you very much!
Add
user = models.ForeignKey(User, on_delete=models.CASCADE)
to Projecto model. Then, in your view, set project.user = self.request.user before saving your project model.
I think you are doing it completely wrong.
You shouldn't be using get_queryset() at all in CreateView - https://stackoverflow.com/a/24043478/4626254
Here's is what you can try instead.
Add a user field in Project model and apply migrations.
user = models.ForeignKey(User, on_delete=models.CASCADE)
Create a class inheriting Generic APIView instead of CreateView.
Create a POST method like def post(self, request): inside that class and get all the details for creating a Projeto object in the request payload using request.data or request.POST.
Get the logged in user using request.user
Create a Projecto object with all this information as Projeto.objects.create(**your_other_fields, user=request.user)
Next time when filtering the objects, use a filter on user field like user=request.user.

Can I somehow call logic from form_valid() in Django tests?

I am trying to test form for Post model creation in my simple forum application. The problem I am having is that after I try to save the form in tests I get an error NOT NULL constraint failed: forum_web_post.user_id because I am assigning the user in the form_valid() method in the view. The user is not passed via the form since the user that creates the post is the signed in user that sent the request.
models.py
class Post(models.Model):
category = models.ForeignKey(Category, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
text = models.TextField()
created_at = models.DateTimeField(auto_now=True)
user is imported form django.contrib.auth.models and Category model looks like this.
class Category(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now=True)
in views.py after the user submits the form he is the redirected to his profile page
views.py
class PostCreate(generic.CreateView):
model = Post
form_class = PostForm
template_name = 'forum_web/post_edit.html'
def form_valid(self, form):
post = form.save(commit=False)
post.user = models.User.objects.get(id=self.request.user.id)
post.save()
return HttpResponseRedirect(reverse_lazy('forum:user_detail', kwargs={'pk': self.request.user.id}))
forms.py
class PostForm(ModelForm):
class Meta:
model = Post
fields = ['category', 'title', 'text']
tests.py
def test_post_form_create_should_pass(self):
# this dict is in setUp() as well as the test_category but for simplicity I will include it in this method
post_data = {
'category': self.test_category.pk,
'title': 'test_post',
'text': 'test_post_text'
}
post_count = Post.objects.count()
form = PostForm(data=self.post_data)
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(post_count + 1, Post.objects.count())
any help would be appreciated!
You are just testing the form. So it doesn't make sense to call a view function in your test. You should manually assign a user to the form's instance using form.instance.user = ... before saving the form in your test, since that's the way the form should be used.
In addition, you should test your view by actually logging in a user and posting the request to the PostCreate view. Here you're testing that the view correctly saves the form (and that your form_valid() method works correctly).
Note: post.user = self.request.user would be better than re-fetching the user from the database using the id. It's redundant.

Django Rest Framework upload file to a method

So i have been trying to upload a file to a method using DRF with no luck so far.
I was able to upload to a ModelViewSet using (FormParser, MultiPartParser,) with no problems, but i really need to use it in something like this http://localhost:8000/api/v1/women/{pk}/upload_avatar/ where i want to first filter the woman by id and upload to her avatar (which is a foreign key to a multimedia model). I tried using a nested resource library with no luck.
So far i have in my modelviewset:
class WomenNativePassportViewSet(viewsets.ModelViewSet):
queryset = Women.objects.all()
serializer_class = WomenNativePassportSerializer
authentication_classes = (NoAuthentication,)
permission_classes = (AllowAny,)
parser_classes = (FormParser, MultiPartParser,)
#detail_route(
methods=['post', 'put', 'patch', 'get'], permission_classes=[AllowAny],
authentication_classes=[NoAuthentication], serializer_class=MultimediaSerializer,
parser_classes=(FormParser, MultiPartParser,)
)
def upload_avatar(self, request, pk=None, *args, **kwargs):
if 'POST' in request._method or 'PATCH' in request._method:
# Write code to save the file??
else:
multimedia = Multimedia.objects.filter(user_profiles_avatares__pk=pk)
page = self.paginate_queryset(multimedia)
serializer = self.get_pagination_serializer(page)
return Response(serializer.data)
My models:
class Women(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
avatar = models.ForeignKey(
'core.Multimedia', blank=True, null=True,
related_name='user_profiles_avatares'
)
class Multimedia(models.Model):
file = models.FileField(upload_to=upload_to, null=True, blank=True)
thumbnail = models.FileField(upload_to=upload_to, null=True, blank=True)
Basically i want to know if this is the right path i am taking, and if yes how can i properly save the uploaded file in the model??
Here is some code of what i did to overcome this problem. Although Kevin Brown answer probably works, i find my code a little "easier" approach:
#detail_route(
methods=['post', 'put', 'patch', 'get'], permission_classes=[AllowAny],
authentication_classes=[NoAuthentication], serializer_class=MultimediaSerializer,
parser_classes=(FormParser, MultiPartParser,)
)
def upload_avatar(self, request, pk=None):
# Because we are using nested resources this was the only way i found to
# upload a file. Maybe there is a better way
if request.method in ['PATCH', 'POST']:
avatar = request.FILES.get('avatar')
if not avatar:
return Response(status=404)
try:
woman = WomenNativePassport.objects.get(pk=pk)
except WomenNativePassport.DoesNotExist:
return Response(status=404)
else:
request.FILES['thumbnail'] = request.FILES['avatar']
serializer = AvatarSerializer(
data=request.DATA, files=request.FILES
)
if serializer.is_valid():
woman.avatar.thumbnail.save(str(avatar), File(avatar))
return Response(status=204)
else:
return Response(status=404)
else:
multimedia = Multimedia.objects.filter(user_profiles_avatares__pk=pk)
page = self.paginate_queryset(multimedia)
serializer = self.get_pagination_serializer(page)
return Response(serializer.data)
# serializer
class AvatarSerializer(serializers.Serializer):
thumbnail = serializers.ImageField()
Any uploaded files should be available in request.FILES, a dictionary keyed by the field that they was used when uploading. Once you have the file, it's a matter of handling it similar to any other uploaded file in Django.
If you can, I would use a second serializer that wraps the Multimedia model so the image validation and saving can be done automatically through Django REST Framework. There is an ImageField that will automatically validate the image by Pillow which you can use on the serializer.

Categories

Resources