# Duplicate object with django-hitcount

**URL:** <https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370>\
**Category:** Using the ORM\
**Created:** [November 30, 2022, 12:20pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370 "2022-11-30T12:20:23Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Wersony](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/wersony/32/5941_2.png) [@Wersony](https://forum.djangoproject.com/u/Wersony)\
**Post date:** [November 30, 2022, 12:20pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/1 "2022-11-30T12:20:24Z")

</div>

I want to duplicate my current object with its values. So, I need to copy all of its old values to the new object. Duplication of all old values I implemented well, but I can’t make hits (views) that are created with django-hitcount. I tried to do different options, but it doesn’t work. Could you please give me advice on how to build it?

**models.py**

```auto
class Blog(models.Model, HitCountMixin):
    slug = models.SlugField()
    name = models.CharField()
    author = models.ForeignKey(
        "users.CustomUser",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    content = models.TextField(blank=True, null=True)
    bookmarkscount = models.IntegerField(null=True, blank=True, default=0)
    downloadscount = models.IntegerField(null=True, blank=True, default=0)
    hit_count_generic = GenericRelation(
        HitCount,
        object_id_field="object_pk",
        related_query_name="hit_count_generic_relation",
    )

```

**hitcount.models.py**

```auto
from datetime import timedelta

from django.db import models
from django.conf import settings
from django.db.models import F
from django.utils import timezone
from django.dispatch import receiver
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _

from etc.toolbox import get_model_class_from_string

AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')

from .managers import HitCountManager, HitManager
from .settings import MODEL_HITCOUNT
from .signals import delete_hit_count

@receiver(delete_hit_count)
def delete_hit_count_handler(sender, instance, save_hitcount=False, **kwargs):
    """
    Custom callback for the Hit.delete() method.

    Hit.delete(): removes the hit from the associated HitCount object.
    Hit.delete(save_hitcount=True): preserves the hit for the associated
    HitCount object.

    """
    if not save_hitcount:
        instance.hitcount.decrease()

class HitCountBase(models.Model):
    """
    Base class for hitcount models.

    Model that stores the hit totals for any content object.

    """
    hits = models.PositiveIntegerField(default=0)
    modified = models.DateTimeField(auto_now=True)
    content_type = models.ForeignKey(
        ContentType, related_name="content_type_set_for_%(class)s", on_delete=models.CASCADE)
    object_pk = models.PositiveIntegerField(verbose_name='object ID')
    content_object = GenericForeignKey('content_type', 'object_pk')

    objects = HitCountManager()

    class Meta:
        abstract = True
        ordering = ('-hits',)
        get_latest_by = "modified"
        verbose_name = _("hit count")
        verbose_name_plural = _("hit counts")
        unique_together = ("content_type", "object_pk")

    def __str__ (self):
        return '%s' % self.content_object

    def increase(self):
        self.hits = F('hits') + 1
        self.save()

    def decrease(self):
        self.hits = F('hits') - 1
        self.save()

    def hits_in_last(self, **kwargs):
        """
        Returns hit count for an object during a given time period.

        This will only work for as long as hits are saved in the Hit database.
        If you are purging your database after 45 days, for example, that means
        that asking for hits in the last 60 days will return an incorrect
        number as that the longest period it can search will be 45 days.

        For example: hits_in_last(days=7).

        Accepts days, seconds, microseconds, milliseconds, minutes,
        hours, and weeks. It's creating a datetime.timedelta object.

        """
        assert kwargs, "Must provide at least one timedelta arg (eg, days=1)"

        period = timezone.now() - timedelta(**kwargs)
        return self.hit_set.filter(created__gte=period).count()

    # def get_content_object_url(self):
    # """
    # Django has this in its contrib.comments.model file -- seems worth
    # implementing though it may take a couple steps.
    #
    # """
    # pass

class HitCount(HitCountBase):
    """Built-in hitcount class. Default functionality."""

    class Meta(HitCountBase.Meta):
        db_table = "hitcount_hit_count"

class Hit(models.Model):
    """
    Model captures a single Hit by a visitor.

    None of the fields are editable because they are all dynamically created.
    Browsing the Hit list in the Admin will allow one to blacklist both
    IP addresses as well as User Agents. Blacklisting simply causes those
    hits to not be counted or recorded.

    Depending on how long you set the HITCOUNT_KEEP_HIT_ACTIVE, and how long
    you want to be able to use `HitCount.hits_in_last(days=30)` you can choose
    to clean up your Hit table by using the management `hitcount_cleanup`
    management command.

    """
    created = models.DateTimeField(editable=False, auto_now_add=True, db_index=True)
    ip = models.CharField(max_length=40, editable=False, db_index=True)
    session = models.CharField(max_length=40, editable=False, db_index=True)
    user_agent = models.CharField(max_length=255, editable=False)
    user = models.ForeignKey(AUTH_USER_MODEL, null=True, editable=False, on_delete=models.CASCADE)
    hitcount = models.ForeignKey(MODEL_HITCOUNT, editable=False, on_delete=models.CASCADE)

    objects = HitManager()

    class Meta:
        ordering = ('-created',)
        get_latest_by = 'created'
        verbose_name = _("hit")
        verbose_name_plural = _("hits")

    def __str__ (self):
        return 'Hit: %s' % self.pk

    def save(self, *args, **kwargs):
        """
        The first time the object is created and saved, we increment
        the associated HitCount object by one. The opposite applies
        if the Hit is deleted.

        """
        if self.pk is None:
            self.hitcount.increase()

        super().save(*args, **kwargs)

    def delete(self, save_hitcount=False):
        """
        If a Hit is deleted and save_hitcount=True, it will preserve the
        HitCount object's total. However, under normal circumstances, a
        delete() will trigger a subtraction from the HitCount object's total.

        NOTE: This doesn't work at all during a queryset.delete().

        """
        delete_hit_count.send(
            sender=self, instance=self, save_hitcount=save_hitcount)
        super().delete()

class BlacklistIP(models.Model):

    ip = models.CharField(max_length=40, unique=True)

    class Meta:
        db_table = "hitcount_blacklist_ip"
        verbose_name = _("Blacklisted IP")
        verbose_name_plural = _("Blacklisted IPs")

    def __str__ (self):
        return '%s' % self.ip

class BlacklistUserAgent(models.Model):

    user_agent = models.CharField(max_length=255, unique=True)

    class Meta:
        db_table = "hitcount_blacklist_user_agent"
        verbose_name = _("Blacklisted User Agent")
        verbose_name_plural = _("Blacklisted User Agents")

    def __str__ (self):
        return '%s' % self.user_agent

class HitCountMixin:
    """
    HitCountMixin provides an easy way to add a `hit_count` property to your
    model that will return the related HitCount object.
    """

    @property
    def hit_count(self):
        ctype = ContentType.objects.get_for_model(self. __class__ )
        hit_count, created = get_model_class_from_string(MODEL_HITCOUNT).objects.get_or_create(
            content_type=ctype, object_pk=self.pk)
        return hit_count

```

**views.py**

```auto
def BlogDuplicate(request, slug):
    blog = Blog.objects.get(slug=slug)
    if request.method == "POST":
        form = BlogForm(
            request.POST, request.FILES
        ) 
    if form.is_valid():
       obj = form.save(commit=False)
       obj.slug = blog.slug
       obj.bookmarkscount = blog.bookmarkscount
       obj.downloadscount = blog.downloadscount
       obj.save()
       hits = blog.hit_count.hits # my last try. But it does not work     
       obj.hit_count.hits = blog.hit_count.hits # But it does not work
       obj.save()
       return redirect("blogs")
   return render(
        request,
        "blog.html",
        {
            "form": form,
            "blog": blog,
        },
    )

```

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [November 30, 2022, 1:55pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/2 "2022-11-30T13:55:17Z")

</div>

If you are getting an error message in your console when this is being run, please post the complete traceback here.

If you have tried other things in the spot of those two marked lines, and they have also thrown errors, please post what you’ve tried and the resulting errors.

What I first notice is that you’ve got your Mixin after the base Model class. Mixins _must_ always be listed before the base class in class definitions.

---

<div class="post-metadata">

**Author:** ![Wersony](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/wersony/32/5941_2.png) [@Wersony](https://forum.djangoproject.com/u/Wersony)\
**Post date:** [November 30, 2022, 5:35pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/3 "2022-11-30T17:35:26Z")

</div>

Ken, hello!

> [@KenWhitesell](#):
>
> If you are getting an error message in your console when this is being run, please post the complete traceback here.

I don’t have an error message in my console. It just does not save hits in new object.

> [@KenWhitesell](#):
>
> If you have tried other things in the spot of those two marked lines, and they have also thrown errors, please post what you’ve tried and the resulting errors.

Yes, of course. Below are my attempts. But I’m sorry I had forgotten my other attempts

```auto
def BlogDuplicate(request, slug):
    blog = Blog.objects.get(slug=slug)
    if request.method == "POST":
        form = BlogForm(
            request.POST, request.FILES
        ) 
    if form.is_valid():
       obj = form.save(commit=False)
       obj.slug = blog.slug
       obj.bookmarkscount = blog.bookmarkscount
       obj.downloadscount = blog.downloadscount
       obj.save()
       hits = blog.hit_count.hits # my try. But it does not work     
       obj.hit_count.hits = blog.hit_count.hits # 1. You saw it

       obj.hit_count_generic = hits # 2. 

       obj.hit_count_generic.add(*hits) # 3.

       new_id = obj.hit_count.object_pk # 4.
       content_object = obj.hit_count.content_object # 4.
       content_type_id = obj.hit_count.content_type_id # 4.
       new_hit, created = HitCount.objects.get_or_create( # 4.                   
                object_pk=new_id,                         
                content_object=content_object,
                content_type_id=content_type_id,               
                hits=hits,                                
            )                                             
                                        
       ct = ContentType.objects.get_for_model(obj) # 5.
       new_hit, created = HitCount.objects.get_or_create( # 5.
                content_type_id=ct.id,
                hits=hits,
            )      
       new_hit.save()                                    
       obj.save()                                                 
       return redirect("blogs")
   return render(
        request,
        "blog.html",
        {
            "form": form,
            "blog": blog,
        },
    )

```

1. Error: Direct assignment to the reverse side of a related set is prohibited. Use hit\_count\_generic\_relation.set() instead.
2. add() argument after \* must be an iterable, not int
3. Field ‘content\_object’ does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
4. It just does not save hits in new object.

> [@KenWhitesell](#):
>
> What I first notice is that you’ve got your Mixin after the base Model class. Mixins _must_ always be listed before the base class in class definitions.

Thank you! I haven’t known it. I swapped them out but unfortunately nothing has changed

Ken, my suggestion, it may be related to “GenericRelation”. But I’m not sure. I tryed to build with it, but it does not work.

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [November 30, 2022, 5:58pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/4 "2022-11-30T17:58:51Z")

</div>

Let’s walk this through:

> [@Wersony](#):
>
> `hits = blog.hit_count.hits`

`blog` is a `Blog` as retrieved at the beginning of the view.

`blog.hit_count` then should be a reference to an instance of MODEL\_HITCOUNT (as defined in your settings), as retrieved by the `hit_count` method in `HitCountMixin`.

You could try printing (or using a debugger on) `blog.hit_count` to verify that that is what it is referencing.

MODEL\_HITCOUNT should be a subclass of HitCountBase. What is MODEL\_HITCOUNT? (By default it is `hitcount.HitCount`, unless you’ve overridden `HITCOUNT_HITCOUNT_MODEL`.)

If it is `HitCount`, then `blog.hit_count.hits` should return the proper value. Have you validated that?

(All this is to ensure you’re getting the right number to start with.)

Now, once you have _that_ value, we can work on setting the new instances.

`obj.hit_count` should return the appropriate instance of MODEL\_HITCOUNT, having the `hits` field to set.

Again, you may want to verify that you have the right instance here.

If so, you’ll want to set the `hits` field to the field retrieved above, and then _save_ that modified instance.

Note, you may want to play with this in the Django shell to figure out what works - it’s going to be a whole lot easier than trying to keep modifying a view and testing it that way.

---

<div class="post-metadata">

**Author:** ![Wersony](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/wersony/32/5941_2.png) [@Wersony](https://forum.djangoproject.com/u/Wersony)\
**Post date:** [December 1, 2022, 3:07pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/5 "2022-12-01T15:07:31Z")

</div>

Ken, thanks for your explanation of how you think when you encounter an error! It’s useful and interesting

> [@KenWhitesell](#):
>
> You could try printing (or using a debugger on) `blog.hit_count` to verify that that is what it is referencing.

I get an object. if I print `blog.hit_count` i get name an object and if I use `blog.hit_count.pk` I get pk an object. Of course, If I print `obj.hit_count` I get name new object.

> [@KenWhitesell](#):
>
> If so, you’ll want to set the `hits` field to the field retrieved above, and then _save_ that modified instance.

I build so. But It does not save hits anyway. I don’t know why.

```auto
def BlogDuplicate(request, slug):
    blog = Blog.objects.get(slug=slug)
    if request.method == "POST":
        form = BlogForm(
            request.POST, request.FILES
        ) 
    if form.is_valid():
       obj = form.save(commit=False)
       obj.slug = blog.slug
       obj.bookmarkscount = blog.bookmarkscount
       obj.downloadscount = blog.downloadscount
       obj.save()
       hits = blog.hit_count #new
       hit = obj.hit_count #new
       hit.hits = hit.hits #new
       hit.save() #new
       return redirect("blogs")
   return render(
        request,
        "blog.html",
        {
            "form": form,
            "blog": blog,
        },
    )

```

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [December 1, 2022, 3:12pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/6 "2022-12-01T15:12:52Z")

</div>

> [@Wersony](#):
>
> `hit.hits = hit.hits`

You’re saving a variable to itself - this line isn’t going to change anything. (I think you meant to write `hit.hits = hits.hits.)

> [@Wersony](#):
>
> … if I use `blog.hit_count.pk` I get pk an object. …

But what about if you print `blog.hit_count.hits`? Do you get the number you’re expecting to see? (That’s _really_ the value you’re trying to verify here.)

---

<div class="post-metadata">

**Author:** ![Wersony](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/wersony/32/5941_2.png) [@Wersony](https://forum.djangoproject.com/u/Wersony)\
**Post date:** [December 1, 2022, 4:03pm UTC](https://forum.djangoproject.com/t/duplicate-object-with-django-hitcount/17370/7 "2022-12-01T16:03:14Z")

</div>

> [@KenWhitesell](#):
>
> You’re saving a variable to itself - this line isn’t going to change anything. (I think you meant to write `hit.hits = hits.hits.)

Ken, that’s my mistake. Now it seems to be saved 🙂 Thank you so much!

This version works.

```auto
def BlogDuplicate(request, slug):
    blog = Blog.objects.get(slug=slug)
    if request.method == "POST":
        form = BlogForm(
            request.POST, request.FILES
        ) 
    if form.is_valid():
       obj = form.save(commit=False)
       obj.slug = blog.slug
       obj.bookmarkscount = blog.bookmarkscount
       obj.downloadscount = blog.downloadscount
       obj.save()
       old = blog.hit_count #new
       new = obj.hit_count #new
       new.hits = old.hits #new
       new.save() #new
       return redirect("blogs")
   return render(
        request,
        "blog.html",
        {
            "form": form,
            "blog": blog,
        },

```
