0

I am currently building a LMS where the admin can upload multiple images to a lesson. I am having some issues rendering these images with vue.js.

In my models.py I have a Photos model which uses a foreignkey to the lesson model. In the admin I can add the images but in the html the images appear as an empty list with the photo id number e.g [4] In the console I can see the photos array with the length of photos in the lesson object but I cant seem to attach it to the frontend.

Here is the vue template:

                    <template>
    <div class="courses">
        <div class="hero is-light">
            <div class="hero-body has-text-centered">
                <h1 class="title">{{ course.title }}</h1>
            </div>
        </div>

        <section class="section">
            <div class="container">
                <div class="columns content">
                    <div class="column is-2">
                        <h2>Table of contents</h2>

                        <ul>
                            <li
                                v-for="lesson in lessons"
                                v-bind:key="lesson.id"
                            >
                                <a @click="setActiveLesson(lesson)">{{ lesson.title }}</a>
                            </li>
                        </ul>
                    </div>

                    <div class="column is-10">
                        <template v-if="$store.state.user.isAuthenticated">
                            <template v-if="activeLesson">
                                <h2>{{ activeLesson.title }}</h2>
                                
                                {{ activeLesson.long_description }}
                                      {{ activeLesson.photos }}

                                <hr>

                                <article 
                                    class="media box"
                                    v-for="comment in comments"
                                    v-bind:key="comment.id"
                                >
                                    <div class="media-content">
                                        <div class="content">
                                            <p>
                                                <strong>{{ comment.name }}</strong> {{ comment.created_at }}<br>
                                                {{ comment.content }}
                                            </p>
                                        </div>
                                    </div>
                                </article>

                                <form v-on:submit.prevent="submitComment()">
                                    <div class="field">
                                        <label class="label">Name</label>
                                        <div class="control">
                                            <input type="text" class="input" v-model="comment.name">
                                        </div>
                                    </div>

                                    <div class="field">
                                        <label class="label">Content</label>
                                        <div class="control">
                                            <textarea class="textarea" v-model="comment.content"></textarea>
                                        </div>
                                    </div>

                                    <div 
                                        class="notification is-danger"
                                        v-for="error in errors"
                                        v-bind:key="error"
                                    >
                                        {{ error }}
                                    </div>

                                    <div class="field">
                                        <div class="control">
                                            <button class="button is-link">Submit</button>
                                        </div>
                                    </div>
                                </form>
                            </template>

                            <template v-else>
                                {{ course.long_description }}
                            </template>
                        </template>

                        <template v-else>
                            <h2>Restricted access</h2>
                            
                            <p>You need to have an account to continue!</p>
                        </template>
                    </div>
                </div>
            </div>
        </section>
    </div>
</template>

<script>
import axios from 'axios'

export default {
    data() {
        return {
            course: {},
            lessons: [],
            comments: [],
            activeLesson: null,
            errors: [],
            comment: {
                name: '',
                content: ''
            }
          
        } 
    }, 
    

    async mounted() {
        console.log('mounted')
        const slug = this.$route.params.slug

        await axios
            .get(`/api/v1/courses/${slug}/`)
            .then(response => {
                console.log(response.data)
                this.course = response.data.course
                this.lessons = response.data.lessons
            })
        document.title = this.course.title + ' | Relate'

    },
    methods: {
        submitComment() {
            console.log('submitComment')
            this.errors = []
            if (this.comment.name === '') {
                this.errors.push('The name must be filled out')
            }
            if (this.comment.content === '') {
                this.errors.push('The content must be filled out')
            }
            if (!this.errors.length) {
                axios
                    .post(`/api/v1/courses/${this.course.slug}/${this.activeLesson.slug}/`, this.comment)
                    .then(response => {
                        this.comment.name = ''
                        this.comment.content = ''
                        this.comments.push(response.data)
                    })
                    .catch(error => {
                        console.log(error)
                    })
            }
        },
        setActiveLesson(lesson) {
            this.activeLesson = lesson
            this.getComments() 
        },
        getComments() {
            axios
                .get(`/api/v1/courses/${this.course.slug}/${this.activeLesson.slug}/get-comments/`)
                .then(response => {
                    console.log(response.data)
                    this.comments = response.data
                })
        }
    }
}
</script>

The models.py:

class Lesson(models.Model):
    DRAFT = 'draft'
    PUBLISHED = 'published'

    CHOICES_STATUS = (
        (DRAFT, 'Draft'),
        (PUBLISHED, 'Published')
    )

    ARTICLE = 'article'
    QUIZ = 'quiz'

    CHOICES_LESSON_TYPE = (
        (ARTICLE, 'Article'),
        (QUIZ, 'Quiz')
    )

    course = models.ForeignKey(Course, related_name='lessons', on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    slug = models.SlugField()
    short_description = models.TextField(blank=True, null=True)
    long_description = models.TextField(blank=True, null=True)
    status = models.CharField(max_length=20, choices=CHOICES_STATUS, default=PUBLISHED)
    lesson_type = models.CharField(max_length=20, choices=CHOICES_LESSON_TYPE, default=ARTICLE)


    def __str__(self):
        return self.title

class Photo(models.Model):
    lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='photos')
    photo = models.ImageField(upload_to ='lesson_images')

    # resizing the image, you can change parameters like size and quality.
    def save(self, *args, **kwargs):
       super(Photo, self).save(*args, **kwargs)
       img = Image.open(self.photo.path)
       if img.height > 1125 or img.width > 1125:
           img.thumbnail((1125,1125))
       img.save(self.photo.path,quality=70,optimize=True) 

And my serializers.py:

class LessonListSerializer(serializers.ModelSerializer):
    # photo = PhotoSerializers(read_only=True, many=True)
    class Meta:
        model = Lesson
        fields = ('id', 'title', 'slug', 'short_description', 'long_description', 'photos')

Any help with this would be really appreciated!

Mark McKeon
  • 169
  • 2
  • 10

1 Answers1

0

Seems like you need to have an <img /> tag in your Vue template and then iterate over the photos (which should be an array) binding the :src to the photo url string, and the :key to a unique id or index.

Something like

<div class="column is-10">
    <template v-if="$store.state.user.isAuthenticated">
        <template v-if="activeLesson">
            <h2>{{ activeLesson.title }}</h2>
            
            {{ activeLesson.long_description }}
            <img v-for="(photo, index) in activeLesson.photos" :src="photo.url" :key="index" />

            <hr>
Lauren
  • 1,508
  • 1
  • 12
  • 16
  • Ok, just tried it. It didnt work unfortunately - what is the index referring to? – Mark McKeon Dec 27 '21 at 00:41
  • The index is the current index in the array of images, I don't think your data is output in the right format for the images, it needs to be a link to the image. So :src="https://my-url.com/lesson-photo-1" if it's some BLOB or db reference it's not going to work as it's just HTML. – Lauren Dec 27 '21 at 00:45
  • I have the images saved in the django media/uploads/lesson_images folder? – Mark McKeon Dec 27 '21 at 00:48
  • So is there some way to programatically iterate through those urls from the data in activeLesson object? So activelesson.url should give you for example: './mylocalfolder/lesson-image-1.jpg' you need to output the source reference of the image whether it's a hosted URL or locally. https://stackoverflow.com/questions/45116796/how-to-import-and-use-image-in-a-vue-single-file-component#45116994 – Lauren Dec 27 '21 at 01:02
  • Im still not getting any images to appear even when I patch in the file path in :src. Is this because I am using the DRF? – Mark McKeon Dec 27 '21 at 08:56