curtisp
curtisp

Reputation: 2325

Django taggit how to record id of user that added tag

I have Django app using django-taggit module. My work flow process is to add a photo, then come back to photo to assign tags using a photo_edit view, photo_edit form and photo_edit.html template as shown below.

The app successfully tags the photo, using the 'tags': TagWidget() in the form and template. This works fine, and it can save new, modified or deleted tags against that photo.

But I want to be able to record the user id of person adding tag in the django taggit model database table. django taggit doesn't have this feature.

To get started, I have successfully added a new user field to the class TagBase(models.Model): using settings.AUTH_USER_MODEL to refer to user object which creates new user_id column in the taggit_tags table when makemigrations is run.

Now I need to figure out how to get current user id to populate that new user_id column when adding or editing tagged object tags.

I have tried doing this in both my app view and django taggit model. Ideally, since this would be a default feature, the user id is saved in django taggit model.

In django taggit model I have tried adding self.user = get_user_model().objects.get(id=request.user.id) to the TagBase def save function. But of course it doesn't know what request is. I also tried creating new function in the TagBase model class as below, and calling it from the def save function but it returns nothing.

def get_user(request):
    user = request.user
    print('MODEL request user', request.user)
    return user

As a test, I replaced request.user.id with hard coded id eg self.user = get_user_model().objects.get(id=1) which actually does write 1 to the taggit_tag table user_id field for that tag.

I tried doing this in the app view too. For example, I have tried photo.tag.user_id = request.user.id before save_m2m() which didn't work. It is not clear what syntax, if any, is available to save related model fields. I believe I would have to modify the form TagWidget() to include user_id field so save_2m2() would save it, but want to avoid modifying django-taggit too much.

I have searched exhaustively and have not found anything specific to Django and django-taggit. Perhaps someone could help from Python perspective!

Photo edit form

class PhotoEditForm(ModelForm):
    #filename = forms.ImageField()
    class Meta:
        model = Photo
        fields = (
            'tags',
        )
        widgets = {
            'tags': TagWidget(),
        }

Photo edit view - ideally i could save request.user before form.save_m2m() something like photo.taggit_tag.user_id = user.id. Is there some syntax that would work here?

def photo_edit(request, photo_id):
    photo = Photo.objects.get(id=int(photo_id))
    user = get_user_model().objects.get(id=request.user.id)
    photo_tags = Tag.objects.filter(photo=photo.id).order_by('name')

    if request.method == "POST":
        form = PhotoEditForm(request.POST, instance=photo)

        if form.is_valid():
            photo = form.save(commit=False)
            photo.modified = timezone.now()
            photo = form.save()

            #save tags if any
            form.save_m2m()

            return HttpResponseRedirect(reverse('photo', kwargs={ 'photo_id': photo.id,}))

    else:
        form = PhotoEditForm(instance=photo)

    context = { 
        'photo_form': form,
        'photo': photo, 
        'photo_tags': photo_tags,
        'title': 'Edit Mode',
        }

    return render(
        request,
        'photo_edit.html',
        context,

    )  

Photo model

class Photo(models.Model):
    filename = CloudinaryField('image')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=True)
    inactive = models.BooleanField(default=False, choices=INACTIVE_CHOICES)
    created = models.DateTimeField(blank=True, null=True)
    modified  = models.DateTimeField(blank=True, null=True)
    tags = TaggableManager()

    class Meta:
        managed = True
        db_table = 'photo'
        verbose_name_plural = 'photos'

    def __str__(self):
        return str(self.filename)

I did make slight modification of django taggit model to include user_id field. Ideally i could set this user_id to request.user as default value but haven't seen anything yet that looks simple or easy.

class TagBase(models.Model):
    name = models.CharField(verbose_name=_("Name"), unique=True, max_length=100)
    slug = models.SlugField(verbose_name=_("Slug"), unique=True, max_length=100)
    # added user to model which created `user_id` column on `taggit_tags` table
    user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=True)

    def __str__(self):
        return self.name

    def __gt__(self, other):
        return self.name.lower() > other.name.lower()

    def __lt__(self, other):
        return self.name.lower() < other.name.lower()

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        self.user = get_user_model().objects.get(id=1)
        if self._state.adding and not self.slug:
            self.slug = self.slugify(self.name)
            using = kwargs.get("using") or router.db_for_write(
                type(self), instance=self
            )

Upvotes: 0

Views: 538

Answers (1)

jmcarson
jmcarson

Reputation: 446

You may want to look at django-crum: Django-CRUM (Current Request User Middleware) captures the current request and user in thread local storage.

I used it when tackling a similar issue. It allows you to access the current user without the request object having to be passed directly:

https://django-crum.readthedocs.io/en/stable/

Upvotes: 1

Related Questions