From d499b781d4d349561068d7af7ef07fd62cd6d8ef Mon Sep 17 00:00:00 2001
From: Jan Dittberner 
Date: Sat, 15 Apr 2023 11:48:53 +0200
Subject: [PATCH 1/3] Implement impersonation
---
 gnuviechadmin/gnuviechadmin/settings.py       | 70 ++++++++++---------
 gnuviechadmin/gnuviechadmin/urls.py           |  3 +-
 gnuviechadmin/templates/base.html             |  9 ++-
 .../templates/impersonate/list_users.html     | 31 ++++++++
 .../templates/impersonate/search_users.html   | 45 ++++++++++++
 poetry.lock                                   | 13 +++-
 pyproject.toml                                |  1 +
 7 files changed, 135 insertions(+), 37 deletions(-)
 create mode 100644 gnuviechadmin/templates/impersonate/list_users.html
 create mode 100644 gnuviechadmin/templates/impersonate/search_users.html
diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py
index b1a6d1c..243b6e8 100644
--- a/gnuviechadmin/gnuviechadmin/settings.py
+++ b/gnuviechadmin/gnuviechadmin/settings.py
@@ -86,7 +86,6 @@ USE_TZ = True
 
 LOCALE_PATHS = (normpath(join(SITE_ROOT, "gnuviechadmin", "locale")),)
 
-
 # ######### MEDIA CONFIGURATION
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
 MEDIA_ROOT = normpath(join(SITE_ROOT, "media"))
@@ -180,7 +179,6 @@ AUTHENTICATION_BACKENDS = (
     "allauth.account.auth_backends.AuthenticationBackend",
 )
 
-
 # ######### URL CONFIGURATION
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
 ROOT_URLCONF = "%s.urls" % SITE_NAME
@@ -208,6 +206,7 @@ DJANGO_APPS = (
     # Flatpages for about page
     "django.contrib.flatpages",
     "crispy_forms",
+    "impersonate",
 )
 
 ALLAUTH_APPS = (
@@ -277,7 +276,7 @@ LOGGING = {
     "formatters": {
         "verbose": {
             "format": "%(levelname)s %(asctime)s %(name)s "
-            "%(module)s:%(lineno)d %(process)d %(thread)d %(message)s"
+                      "%(module)s:%(lineno)d %(process)d %(thread)d %(message)s"
         },
         "simple": {"format": "%(levelname)s %(name)s:%(lineno)d %(message)s"},
     },
@@ -366,7 +365,10 @@ def show_debug_toolbar(request):
 # See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa
 INSTALLED_APPS += ("debug_toolbar",)
 
-MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
+MIDDLEWARE += [
+    "impersonate.middleware.ImpersonateMiddleware",
+    "debug_toolbar.middleware.DebugToolbarMiddleware",
+]
 
 DEBUG_TOOLBAR_CONFIG = {
     "SHOW_TOOLBAR_CALLBACK": "gnuviechadmin.settings.show_debug_toolbar"
@@ -403,21 +405,21 @@ if GVA_ENVIRONMENT == "local":
             [
                 (key, {"handlers": ["console"], "level": "DEBUG", "propagate": True})
                 for key in [
-                    "dashboard",
-                    "domains",
-                    "fileservertasks",
-                    "gvacommon",
-                    "gvawebcore",
-                    "hostingpackages",
-                    "ldaptasks",
-                    "managemails",
-                    "mysqltasks",
-                    "osusers",
-                    "pgsqltasks",
-                    "taskresults",
-                    "userdbs",
-                    "websites",
-                ]
+                "dashboard",
+                "domains",
+                "fileservertasks",
+                "gvacommon",
+                "gvawebcore",
+                "hostingpackages",
+                "ldaptasks",
+                "managemails",
+                "mysqltasks",
+                "osusers",
+                "pgsqltasks",
+                "taskresults",
+                "userdbs",
+                "websites",
+            ]
             ]
         )
     )
@@ -438,21 +440,21 @@ elif GVA_ENVIRONMENT == "test":
             [
                 (key, {"handlers": ["console"], "level": "ERROR", "propagate": True})
                 for key in [
-                    "dashboard",
-                    "domains",
-                    "fileservertasks",
-                    "gvacommon",
-                    "gvawebcore",
-                    "hostingpackages",
-                    "ldaptasks",
-                    "managemails",
-                    "mysqltasks",
-                    "osusers",
-                    "pgsqltasks",
-                    "taskresults",
-                    "userdbs",
-                    "websites",
-                ]
+                "dashboard",
+                "domains",
+                "fileservertasks",
+                "gvacommon",
+                "gvawebcore",
+                "hostingpackages",
+                "ldaptasks",
+                "managemails",
+                "mysqltasks",
+                "osusers",
+                "pgsqltasks",
+                "taskresults",
+                "userdbs",
+                "websites",
+            ]
             ]
         )
     )
diff --git a/gnuviechadmin/gnuviechadmin/urls.py b/gnuviechadmin/gnuviechadmin/urls.py
index 8be802e..8bcfca5 100644
--- a/gnuviechadmin/gnuviechadmin/urls.py
+++ b/gnuviechadmin/gnuviechadmin/urls.py
@@ -11,6 +11,8 @@ admin.autodiscover()
 
 urlpatterns = [
     re_path(r"", include("dashboard.urls")),
+    re_path(r"^admin/", admin.site.urls),
+    re_path(r"^impersonate/", include("impersonate.urls")),
     re_path(r"^accounts/", include("allauth.urls")),
     re_path(r"^database/", include("userdbs.urls")),
     re_path(r"^domains/", include("domains.urls")),
@@ -18,7 +20,6 @@ urlpatterns = [
     re_path(r"^website/", include("websites.urls")),
     re_path(r"^mail/", include("managemails.urls")),
     re_path(r"^osuser/", include("osusers.urls")),
-    re_path(r"^admin/", admin.site.urls),
     re_path(r"^contact/", include("contact_form.urls")),
     re_path(r"^impressum/$", views.flatpage, {"url": "/impressum/"}, name="imprint"),
 ]
diff --git a/gnuviechadmin/templates/base.html b/gnuviechadmin/templates/base.html
index 8040f63..de0c35b 100644
--- a/gnuviechadmin/templates/base.html
+++ b/gnuviechadmin/templates/base.html
@@ -71,6 +71,7 @@
             
                {% trans "My Account" %} 
               
+
+    
+        {% trans "List all users" %}
+    
+
+    
+        {% if query and page.object_list %}
+            
+                {% for user in page.object_list %}
+                    - {{ user }} - Impersonate
+                    +                {% endfor %}
+
+        {% endif %}
+
+
+    
+        {% if query and page.has_previous %}
+            Previous
+                Page  
+        {% endif %}
+
+        {% if query and page.has_next %}
+            Next
+                Page
+        {% endif %}
+    
+{% endblock %}
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index 720f7ea..9825743 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -666,6 +666,17 @@ files = [
 django = ">=3.2.4"
 sqlparse = ">=0.2"
 
+[[package]]
+name = "django-impersonate"
+version = "1.9.1"
+description = "Django app to allow superusers to impersonate other users."
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+    {file = "django-impersonate-1.9.1.tar.gz", hash = "sha256:0befdb096198b458507239a6f21574c9e0f608ab01fad352d71eb9284e5bb9c9"},
+]
+
 [[package]]
 name = "django-model-utils"
 version = "4.3.1"
@@ -1742,4 +1753,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.7"
-content-hash = "6041c8bb49cd1df098f1948f8ad2cbd48fd8f42ff44e410f3fecb61be7e80a18"
+content-hash = "dd56e0233689448f08dfcae943871bf9d72c05ad7bfd326c69f9ecb33ea8a461"
diff --git a/pyproject.toml b/pyproject.toml
index 3f3b448..3fcd8d1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,6 +19,7 @@ gvacommon = {version = "^0.6.0", source = "gnuviech"}
 passlib = "^1.7.4"
 redis = "^4.5.1"
 requests-oauthlib = "^1.3.1"
+django-impersonate = "^1.9.1"
 
 
 [tool.poetry.group.dev.dependencies]
From 345b32f28679cb0d40e5dba4d08ec7c61c2ae928 Mon Sep 17 00:00:00 2001
From: Jan Dittberner 
Date: Sat, 15 Apr 2023 12:06:44 +0200
Subject: [PATCH 2/3] Remove unused powerdns support tables
---
 gnuviechadmin/domains/admin.py                |  19 +-
 .../0005_remove_unused_pdns_tables.py         |  74 ++++
 gnuviechadmin/domains/models.py               | 330 ------------------
 3 files changed, 75 insertions(+), 348 deletions(-)
 create mode 100644 gnuviechadmin/domains/migrations/0005_remove_unused_pdns_tables.py
diff --git a/gnuviechadmin/domains/admin.py b/gnuviechadmin/domains/admin.py
index 87be497..0369116 100644
--- a/gnuviechadmin/domains/admin.py
+++ b/gnuviechadmin/domains/admin.py
@@ -5,24 +5,7 @@ with the django admin site.
 """
 from django.contrib import admin
 
-from .models import (
-    DNSComment,
-    DNSCryptoKey,
-    DNSDomain,
-    DNSDomainMetadata,
-    DNSRecord,
-    DNSSupermaster,
-    DNSTSIGKey,
-    HostingDomain,
-    MailDomain,
-)
+from domains.models import HostingDomain, MailDomain
 
 admin.site.register(MailDomain)
 admin.site.register(HostingDomain)
-admin.site.register(DNSComment)
-admin.site.register(DNSCryptoKey)
-admin.site.register(DNSDomain)
-admin.site.register(DNSDomainMetadata)
-admin.site.register(DNSRecord)
-admin.site.register(DNSSupermaster)
-admin.site.register(DNSTSIGKey)
diff --git a/gnuviechadmin/domains/migrations/0005_remove_unused_pdns_tables.py b/gnuviechadmin/domains/migrations/0005_remove_unused_pdns_tables.py
new file mode 100644
index 0000000..87e9c32
--- /dev/null
+++ b/gnuviechadmin/domains/migrations/0005_remove_unused_pdns_tables.py
@@ -0,0 +1,74 @@
+# Generated by Django 3.2.18 on 2023-04-15 09:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('domains', '0004_auto_20151107_1708'),
+    ]
+
+    operations = [
+        migrations.AlterIndexTogether(
+            name='dnscomment',
+            index_together=None,
+        ),
+        migrations.RemoveField(
+            model_name='dnscomment',
+            name='customer',
+        ),
+        migrations.RemoveField(
+            model_name='dnscomment',
+            name='domain',
+        ),
+        migrations.RemoveField(
+            model_name='dnscryptokey',
+            name='domain',
+        ),
+        migrations.RemoveField(
+            model_name='dnsdomain',
+            name='customer',
+        ),
+        migrations.RemoveField(
+            model_name='dnsdomainmetadata',
+            name='domain',
+        ),
+        migrations.AlterIndexTogether(
+            name='dnsrecord',
+            index_together=None,
+        ),
+        migrations.RemoveField(
+            model_name='dnsrecord',
+            name='domain',
+        ),
+        migrations.AlterUniqueTogether(
+            name='dnssupermaster',
+            unique_together=None,
+        ),
+        migrations.RemoveField(
+            model_name='dnssupermaster',
+            name='customer',
+        ),
+        migrations.DeleteModel(
+            name='DNSTSIGKey',
+        ),
+        migrations.DeleteModel(
+            name='DNSComment',
+        ),
+        migrations.DeleteModel(
+            name='DNSCryptoKey',
+        ),
+        migrations.DeleteModel(
+            name='DNSDomain',
+        ),
+        migrations.DeleteModel(
+            name='DNSDomainMetadata',
+        ),
+        migrations.DeleteModel(
+            name='DNSRecord',
+        ),
+        migrations.DeleteModel(
+            name='DNSSupermaster',
+        ),
+    ]
diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py
index 00a96e3..7332dd6 100644
--- a/gnuviechadmin/domains/models.py
+++ b/gnuviechadmin/domains/models.py
@@ -7,45 +7,8 @@ from __future__ import absolute_import
 from django.conf import settings
 from django.db import models, transaction
 from django.utils.translation import gettext as _
-from model_utils import Choices
 from model_utils.models import TimeStampedModel
 
-DNS_DOMAIN_TYPES = Choices(
-    ("MASTER", _("Master")),
-    ("SLAVE", _("Slave")),
-    ("NATIVE", _("Native")),
-)
-
-# see https://doc.powerdns.com/md/authoritative/domainmetadata/
-DNS_DOMAIN_METADATA_KINDS = Choices(
-    "ALLOW-DNSUPDATE-FROM",
-    "ALSO-NOTIFY",
-    "AXFR-MASTER-TSIG",
-    "AXFR-SOURCE",
-    "FORWARD-DNSUPDATE",
-    "GSS-ACCEPTOR-PRINCIPAL",
-    "GSS-ALLOW-AXFR-PRINCIPAL",
-    "LUA-AXFR-SCRIPT",
-    "NSEC3NARROW",
-    "NSEC3PARAM",
-    "PRESIGNED",
-    "PUBLISH_CDNSKEY",
-    "PUBLISH_CDS",
-    "SOA-EDIT",
-    "SOA-EDIT-DNSUPDATE",
-    "TSIG-ALLOW-AXFR",
-    "TSIG-ALLOW-DNSUPDATE",
-)
-
-DNS_TSIG_KEY_ALGORITHMS = Choices(
-    ("hmac-md5", _("HMAC MD5")),
-    ("hmac-sha1", _("HMAC SHA1")),
-    ("hmac-sha224", _("HMAC SHA224")),
-    ("hmac-sha256", _("HMAC SHA256")),
-    ("hmac-sha384", _("HMAC SHA384")),
-    ("hmac-sha512", _("HMAC SHA512")),
-)
-
 
 class DomainBase(TimeStampedModel):
     """
@@ -140,296 +103,3 @@ class HostingDomain(DomainBase):
 
     def __str__(self):
         return self.domain
-
-
-class DNSDomain(DomainBase):
-    """
-    This model represents a DNS zone. The model is similar to the domain table
-    in the PowerDNS schema specified in
-    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
-
-    .. code-block:: sql
-
-       CREATE TABLE domains (
-         id                    SERIAL PRIMARY KEY,
-         name                  VARCHAR(255) NOT NULL,
-         master                VARCHAR(128) DEFAULT NULL,
-         last_check            INT DEFAULT NULL,
-         type                  VARCHAR(6) NOT NULL,
-         notified_serial       INT DEFAULT NULL,
-         account               VARCHAR(40) DEFAULT NULL,
-         CONSTRAINT c_lowercase_name CHECK (
-             ((name)::TEXT = LOWER((name)::TEXT)))
-       );
-
-       CREATE UNIQUE INDEX name_index ON domains(name);
-
-    """
-
-    # name is represented by domain
-    master = models.CharField(max_length=128, blank=True, null=True)
-    last_check = models.IntegerField(null=True)
-    domaintype = models.CharField(
-        max_length=6, choices=DNS_DOMAIN_TYPES, db_column="type"
-    )
-    notified_serial = models.IntegerField(null=True)
-    # account is represented by customer_id
-    # check constraint is added via RunSQL in migration
-
-    class Meta:
-        verbose_name = _("DNS domain")
-        verbose_name_plural = _("DNS domains")
-
-    def __str__(self):
-        return self.domain
-
-
-class DNSRecord(models.Model):
-    """
-    This model represents a DNS record. The model is similar to the record
-    table in the PowerDNS schema specified in
-    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
-
-    .. code-block:: sql
-
-       CREATE TABLE records (
-         id                    SERIAL PRIMARY KEY,
-         domain_id             INT DEFAULT NULL,
-         name                  VARCHAR(255) DEFAULT NULL,
-         type                  VARCHAR(10) DEFAULT NULL,
-         content               VARCHAR(65535) DEFAULT NULL,
-         ttl                   INT DEFAULT NULL,
-         prio                  INT DEFAULT NULL,
-         change_date           INT DEFAULT NULL,
-         disabled              BOOL DEFAULT 'f',
-         ordername             VARCHAR(255),
-         auth                  BOOL DEFAULT 't',
-         CONSTRAINT domain_exists
-         FOREIGN KEY(domain_id) REFERENCES domains(id)
-         ON DELETE CASCADE,
-         CONSTRAINT c_lowercase_name CHECK (
-             ((name)::TEXT = LOWER((name)::TEXT)))
-       );
-
-       CREATE INDEX rec_name_index ON records(name);
-       CREATE INDEX nametype_index ON records(name,type);
-       CREATE INDEX domain_id ON records(domain_id);
-       CREATE INDEX recordorder ON records (
-           domain_id, ordername text_pattern_ops);
-
-    """
-
-    domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
-    name = models.CharField(max_length=255, blank=True, null=True, db_index=True)
-    recordtype = models.CharField(
-        max_length=10, blank=True, null=True, db_column="type"
-    )
-    content = models.CharField(max_length=65535, blank=True, null=True)
-    ttl = models.IntegerField(null=True)
-    prio = models.IntegerField(null=True)
-    change_date = models.IntegerField(null=True)
-    disabled = models.BooleanField(default=False)
-    ordername = models.CharField(max_length=255)
-    auth = models.BooleanField(default=True)
-    # check constraint and index recordorder are added via RunSQL in migration
-
-    class Meta:
-        verbose_name = _("DNS record")
-        verbose_name_plural = _("DNS records")
-        index_together = [["name", "recordtype"]]
-
-    def __str__(self):
-        return "{name} IN {type} {content}".format(
-            name=self.name, type=self.recordtype, content=self.content
-        )
-
-
-class DNSSupermaster(models.Model):
-    """
-    This model represents the supermasters table in the PowerDNS schema
-    specified in
-    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
-
-    .. code-block:: sql
-
-       CREATE TABLE supermasters (
-         ip                    INET NOT NULL,
-         nameserver            VARCHAR(255) NOT NULL,
-         account               VARCHAR(40) NOT NULL,
-         PRIMARY KEY(ip, nameserver)
-       );
-
-    """
-
-    ip = models.GenericIPAddressField()
-    nameserver = models.CharField(max_length=255)
-    # account is replaced by customer
-    customer = models.ForeignKey(
-        settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE
-    )
-
-    class Meta:
-        verbose_name = _("DNS supermaster")
-        verbose_name_plural = _("DNS supermasters")
-        unique_together = ("ip", "nameserver")
-
-    def __str__(self):
-        return "{ip} {nameserver}".format(ip=self.ip, nameserver=self.nameserver)
-
-
-class DNSComment(models.Model):
-    """
-    This model represents the comments table in the PowerDNS schema specified
-    in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The
-    comments table is used to store user comments related to individual DNS
-    records.
-
-    .. code-block:: sql
-
-       CREATE TABLE comments (
-         id                    SERIAL PRIMARY KEY,
-         domain_id             INT NOT NULL,
-         name                  VARCHAR(255) NOT NULL,
-         type                  VARCHAR(10) NOT NULL,
-         modified_at           INT NOT NULL,
-         account               VARCHAR(40) DEFAULT NULL,
-         comment               VARCHAR(65535) NOT NULL,
-         CONSTRAINT domain_exists
-         FOREIGN KEY(domain_id) REFERENCES domains(id)
-         ON DELETE CASCADE,
-         CONSTRAINT c_lowercase_name CHECK (
-             ((name)::TEXT = LOWER((name)::TEXT)))
-       );
-
-       CREATE INDEX comments_domain_id_idx ON comments (domain_id);
-       CREATE INDEX comments_name_type_idx ON comments (name, type);
-       CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
-
-    """
-
-    domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
-    name = models.CharField(max_length=255)
-    commenttype = models.CharField(max_length=10, db_column="type")
-    modified_at = models.IntegerField()
-    # account is replaced by customer
-    customer = models.ForeignKey(
-        settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE
-    )
-    comment = models.CharField(max_length=65535)
-    # check constraint is added via RunSQL in migration
-
-    class Meta:
-        verbose_name = _("DNS comment")
-        verbose_name_plural = _("DNS comments")
-        index_together = [["name", "commenttype"], ["domain", "modified_at"]]
-
-    def __str__(self):
-        return "{name} IN {type}: {comment}".format(
-            name=self.name, type=self.commenttype, comment=self.comment
-        )
-
-
-class DNSDomainMetadata(models.Model):
-    """
-    This model represents the domainmetadata table in the PowerDNS schema
-    specified in
-    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
-    The domainmetadata table is used to store domain meta data as described in
-    https://doc.powerdns.com/md/authoritative/domainmetadata/.
-
-    .. code-block:: sql
-
-       CREATE TABLE domainmetadata (
-         id                    SERIAL PRIMARY KEY,
-         domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
-         kind                  VARCHAR(32),
-         content               TEXT
-       );
-
-       CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);
-
-    """
-
-    domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
-    kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS)
-    content = models.TextField()
-
-    class Meta:
-        verbose_name = _("DNS domain metadata item")
-        verbose_name_plural = _("DNS domain metadata items")
-
-    def __str__(self):
-        return "{domain} {kind} {content}".format(
-            domain=self.domain.domain, kind=self.kind, content=self.content
-        )
-
-
-class DNSCryptoKey(models.Model):
-    """
-    This model represents the cryptokeys table in the PowerDNS schema
-    specified in
-    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
-
-    .. code-block:: sql
-
-       CREATE TABLE cryptokeys (
-         id                    SERIAL PRIMARY KEY,
-         domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
-         flags                 INT NOT NULL,
-         active                BOOL,
-         content               TEXT
-       );
-
-       CREATE INDEX domainidindex ON cryptokeys(domain_id);
-
-    """
-
-    domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
-    flags = models.IntegerField()
-    active = models.BooleanField(default=True)
-    content = models.TextField()
-
-    class Meta:
-        verbose_name = _("DNS crypto key")
-        verbose_name_plural = _("DNS crypto keys")
-
-    def __str__(self):
-        return "{domain} {content}".format(
-            domain=self.domain.domain, content=self.content
-        )
-
-
-class DNSTSIGKey(models.Model):
-    """
-    This model represents the tsigkeys table in the PowerDNS schema specified
-    in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
-
-    .. code-block:: sql
-
-       CREATE TABLE tsigkeys (
-         id                    SERIAL PRIMARY KEY,
-         name                  VARCHAR(255),
-         algorithm             VARCHAR(50),
-         secret                VARCHAR(255),
-         CONSTRAINT c_lowercase_name CHECK (
-             ((name)::TEXT = LOWER((name)::TEXT)))
-       );
-
-       CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
-
-    """
-
-    name = models.CharField(max_length=255)
-    algorithm = models.CharField(max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS)
-    secret = models.CharField(max_length=255)
-    # check constraint is added via RunSQL in migration
-
-    class Meta:
-        verbose_name = _("DNS TSIG key")
-        verbose_name_plural = _("DNS TSIG keys")
-        unique_together = [["name", "algorithm"]]
-
-    def __str__(self):
-        return "{name} {algorithm} XXXX".format(
-            name=self.name, algorithm=self.algorithm
-        )
From e44bdd7be2270915ee8bef993c8ffc6276f1527b Mon Sep 17 00:00:00 2001
From: Jan Dittberner 
Date: Sat, 15 Apr 2023 12:07:12 +0200
Subject: [PATCH 3/3] Bump version to 0.13.0
---
 gnuviechadmin/gnuviechadmin/__init__.py | 2 +-
 pyproject.toml                          | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py
index 1fd249f..7dfb6e8 100644
--- a/gnuviechadmin/gnuviechadmin/__init__.py
+++ b/gnuviechadmin/gnuviechadmin/__init__.py
@@ -1,4 +1,4 @@
 # import celery_app to initialize it
 from gnuviechadmin.celery import app as celery_app  # NOQA
 
-__version__ = '0.12.1'
+__version__ = "0.13.0"
diff --git a/pyproject.toml b/pyproject.toml
index 3fcd8d1..d28421e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "gva"
-version = "0.12.1"
+version = "0.13.0"
 description = "gnuviechadmin web interface"
 authors = ["Jan Dittberner "]
 license = "AGPL-3+"