Managing an ImageField in Django - Rendering it in an html template

0

I have a custom user model in which I want to add a user avatar field:

class User(AbstractBaseUser, PermissionsMixin):

    # email
    # username
    # first_name
    # last_name  

    slug = models.SlugField(max_length=100,blank=True) 

    avatar = models.ImageField(upload_to='avatars',blank=True,null=True,verbose_name='Photo')

    # Con este signal indico que el valor del campo username se
    # almacene tambien en el campo slug, para generar un URL amigable
    # cuando quiera ir a los datos de un usuario tipo 'preferences/username'
    @receiver(post_save, sender=User)
    def post_save_user(sender, instance, **kwargs):
        slug = slugify(instance.username)
        User.objects.filter(pk=instance.pk).update(slug=slug)

    @property
    def image_url(self):
        if self.avatar and hasattr(self.avatar, 'url'):
            return self.avatar.url

The method image_url() previous, has been defined in that way because when a user is created for the first time, it has no associated avatar or image.

Then when I call in the template the field avatar.url this method examines if field avatar has the attribute url and returns it to paint it in the template in the following way:

<img src="{{ object.image_url|default_if_none:'#' }}" class="img-thumbnail" alt="User Image">

This I'm doing in a template called layout.html of which all my templates inherit.

In case of not being able to extract the url of the avatar, which would occur when a user does not have a photo assigned to be created, then nothing is rendered in the template, or it would have an unknown font in the img tag in its source attribute

In this way I handle a little bit that error that would come out When a user is created, this one has no associated, this error to be more precise:

raise ValueError("The '%s' attribute has no file associated with it." % self.field.name)
ValueError: The 'avatar' attribute has no file associated with it.
[13/Jun/2017 15:10:16] "GET /dashboard/ HTTP/1.1" 500 183409 

This came out because before in the template layout.html called the avatar field in the following way:

<img src="{{ userprofile.user.avatar.url }}" class="img-thumbnail" alt="User Image"> 

userprofile is a context that I send to this template where all the user's data is, including the avatar

Somehow I have managed it that way, having the following workflow:

url(r"^preferences/(?P<slug>[\w.\-]+)/$",
        views.AccountSettingsUpdateView.as_view(),
        name='preferences'
    ),

My form UserUpdateForm() is quite basic where I call several fields of the user among them the one that interests me that is avatar :

class UserUpdateForm(forms.ModelForm):
        class Meta:
            fields = ("first_name", "last_name", "avatar",)
            model = get_user_model()

My view AccountSettingsUpdateView is as follows: As it is said, the view that handles this form will receive the data file (my image of the field avatar in this case) in request.FILES , As is the example , I have dimensioned in some way how the request handling of the form could be in my view, using the post() method:

I think my problem may be here with the request.POST, request.FILES just that I do not get well in this part

class AccountSettingsUpdateView(LoginRequiredMixin, UpdateView):
    # Obtiene mi modelo de usuario personalizado
    model = get_user_model()
    # Obtiene el formulario a utilizar
    form_class = UserUpdateForm
    success_url = reverse_lazy('dashboard')
    context_object_name = 'preferences'

    def post(self, request, *args,**kwargs):
        form = self.get_form()
        if request.method=='POST':
            form = UserUpdateForm(request.POST, request.FILES)
            if form.is_valid():
                form.save()
                return super(AccountSettingsUpdateView, self).form_valid(form)
        else:
            return super(AccountSettingsUpdateView, self).form_invalid(form)


    '''
    # Tenia mis dudas si sobreescribia el form_valid()
    def form_valid(self, form):
        if self.request.method == 'POST':
            form = UserUpdateForm(self.request.POST, self.request.FILES)
            if form.is_valid():
                form.save()
                return super(AccountSettingsUpdateView, self).form_valid(form)
        else:
            return super(AccountSettingsUpdateView, self).form_invalid(form)
        #return render(self.request, 'user_form.html', {'form':form})
    '''

My template user_form.html where I render the form is as follows:

{% extends 'layout.html' %}
{% load bootstrap3 %}

{% block title_tag %}Accounts | Settings | iHost {{ block.super }}{% endblock %}

{% block body_content %}
<div class="container">
    <h1>Account Details</h1>
    <form enctype="multipart/form-data" method="POST">
        {% csrf_token %}
        {% bootstrap_form form %}
        <input type="submit" value="Save Changes" class="btn btn-default">
    </form>
</div>

{% endblock %}

What happens to me is that when I enter the Settings option in my application, which calls the url preferences/(?P<slug>[\w.\-]... referenced above, there my image is rendered because this url calls my view AccountSettingsUpdateView which has the form UserUpdateForm () in which the avatar field is rendered to upload an image.

But when I go to another part of my application, the avatar image is not rendered. I do not know if I explained myself well, but I attach this animated image to explain what happens to me.

How can I do so that my avatar image, which I am rendering it in a base form that all the other templates inherit, persists in any part of my application?

What can be happening? No doubt there must be some detail that I am missing, perhaps in my forms.py or in my view. I'm inclined towards this last one

    
asked by bgarcial 13.06.2017 в 17:41
source

2 answers

1

that happens because the other views do not pass the information of the user, I have already gone through that and what I do is a mixin where in get_context_data I pass the profile information

class UserInformationMixin(object):

    def get_context_data(self, **kwargs):
        context = super(UserInformationMixin, self).get_context_data(**kwargs)
        user = User.object.get(username=self.request.user.username)
        return context

in the template layout.html shows the user's information

{{ user.username }}

updated to show the avatar if the user is authenticated would be more or less like this:

{% if user.is_authenticated %}
    <img src={{ user.avatar.url }}>
{% else %}
    <img src='avatar_default.jpg'>
{% endif %}

or something like that

    
answered by 14.06.2017 / 05:28
source
1

Surely it's not what you're looking for.

But in my case it was resolved with a base64 , there are libraries that take your image and convert it to base64 with javascript so when your image arrives, what arrives is a huge string encoded in base64.

    image_base64 = form.cleanded_data['image_base64']
    image = image_base64.decode('base64')
    bucket = s3.Bucket('mybucket')
    nombreimagen = 'miimagen'
    metadata = {'Content-Type': 'image/jpeg'}
    bucket.put_object(Key="{0}.jpg".format(nombreimagen), Body=image, Metadata=metadata)

And ready that will save the image in the bucket. BUT as you see you need to be in base64. If you want to send the file directly, you must code what Django sends you. For this there are bookstores like pillow (AKA PIL). Which I consider more complex. Because as I mentioned there are many javascript liberias that generate the base64 string that also serves as a preview for the user see the image to be uploaded.

    
answered by 14.06.2017 в 02:37