Browse Source

feat(api): for no-op updates, update RRset.touched

Peter Thomassen 5 years ago
parent
commit
d3fd47638b

+ 20 - 0
api/desecapi/migrations/0014_rrset_touched.py

@@ -0,0 +1,20 @@
+# Generated by Django 3.0.6 on 2020-05-21 10:59
+
+import desecapi.models
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('desecapi', '0013_token_last_used'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='rrset',
+            old_name='updated',
+            new_name='touched',
+        ),
+    ]

+ 2 - 2
api/desecapi/models.py

@@ -370,7 +370,7 @@ class RRsetManager(Manager):
 class RRset(ExportModelOperationsMixin('RRset'), models.Model):
     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
     created = models.DateTimeField(auto_now_add=True)
-    updated = models.DateTimeField(null=True)  # undocumented, used for debugging only
+    touched = models.DateTimeField(null=True)
     domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
     subname = models.CharField(
         max_length=178,
@@ -415,7 +415,7 @@ class RRset(ExportModelOperationsMixin('RRset'), models.Model):
         return self.construct_name(self.subname, self.domain.name)
 
     def save(self, *args, **kwargs):
-        self.updated = timezone.now()
+        self.touched = timezone.now()
         self.full_clean(validate_unique=False)
         super().save(*args, **kwargs)
 

+ 5 - 1
api/desecapi/serializers.py

@@ -8,6 +8,7 @@ from django.contrib.auth.password_validation import validate_password
 from django.core.validators import MinValueValidator
 from django.db import IntegrityError, OperationalError
 from django.db.models import Model, Q
+from django.utils import timezone
 from rest_framework import serializers
 from rest_framework.settings import api_settings
 from rest_framework.validators import UniqueTogetherValidator, UniqueValidator, qs_filter
@@ -299,7 +300,10 @@ class RRsetSerializer(ConditionalExistenceModelSerializer):
         ttl = validated_data.pop('ttl', None)
         if ttl and instance.ttl != ttl:
             instance.ttl = ttl
-            instance.save()
+            instance.save()  # also updates instance.touched
+        else:
+            # Update instance.touched without triggering post-save signal (no pdns action required)
+            models.RRset.objects.filter(pk=instance.pk).update(touched=timezone.now())
 
         return instance
 

+ 9 - 0
api/desecapi/tests/test_rrsets.py

@@ -326,6 +326,15 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             response = self.client.patch_rr_set(self.my_rr_set_domain.name, subname, 'A', {})
             self.assertStatus(response, status.HTTP_200_OK)
 
+    def test_rr_sets_touched_if_noop(self):
+        for subname in self.SUBNAMES:
+            touched_old = RRset.objects.get(domain=self.my_rr_set_domain, type='A', subname=subname).touched
+            response = self.client.patch_rr_set(self.my_rr_set_domain.name, subname, 'A', {})
+            self.assertStatus(response, status.HTTP_200_OK)
+
+            touched_new = RRset.objects.get(domain=self.my_rr_set_domain, type='A', subname=subname).touched
+            self.assertGreater(touched_new, touched_old)
+
     def test_partially_update_other_rr_sets(self):
         data = {'records': ['3.2.3.4'], 'ttl': 334}
         for subname in self.SUBNAMES: