Noot! Domains working
This commit is contained in:
parent
69abba4d36
commit
aa6313b464
|
@ -32,12 +32,12 @@ ALLOWED_HOSTS = []
|
|||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'crispy_forms',
|
||||
'dncore',
|
||||
'whoisdb',
|
||||
|
@ -46,31 +46,31 @@ INSTALLED_APPS = [
|
|||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'dnmgmt.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, "templates/")],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, "templates/")],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'dnmgmt.wsgi.application'
|
||||
|
@ -80,10 +80,10 @@ WSGI_APPLICATION = 'dnmgmt.wsgi.application'
|
|||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,18 +91,18 @@ DATABASES = {
|
|||
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
@ -126,14 +126,13 @@ USE_TZ = True
|
|||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "cstatic")
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
CRISPY_TEMPLATE_PACK = 'bootstrap3'
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger',
|
||||
messages.ERROR: 'danger',
|
||||
}
|
||||
|
||||
AUTH_USER_MODEL = 'dncore.User'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGIN_URL = '/login/'
|
||||
|
||||
LOGIN_URL = '/user/login/'
|
||||
|
|
|
@ -17,25 +17,32 @@ class DomainForm(MntFormMixin, forms.ModelForm):
|
|||
|
||||
super(DomainForm, self).__init__(*args, **kwargs)
|
||||
|
||||
instance = getattr(self, "instance", None)
|
||||
self._create = not (instance and instance.pk)
|
||||
|
||||
if not self._create:
|
||||
self.fields['name'].disabled = True
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name'].lower()
|
||||
if not name.endswith("."):
|
||||
name += "."
|
||||
if self._create:
|
||||
if not name.endswith("."):
|
||||
name += "."
|
||||
|
||||
if not name.endswith("dn."):
|
||||
raise forms.ValidationError("Only .dn domains can be registered at this point")
|
||||
if not name.endswith("dn."):
|
||||
raise forms.ValidationError("Only .dn domains can be registered at this point")
|
||||
|
||||
if name.count(".") > 2:
|
||||
raise forms.ValidationError("No subdomains can be registered")
|
||||
if name.count(".") > 2:
|
||||
raise forms.ValidationError("No subdomains can be registered")
|
||||
|
||||
if not re.match("^[a-z0-9.-]+$", name):
|
||||
raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
|
||||
if not re.match("^[a-z0-9.-]+$", name):
|
||||
raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
|
||||
|
||||
try:
|
||||
Domain.objects.get(name=name)
|
||||
raise forms.ValidationError("Domain already exists")
|
||||
except Domain.DoesNotExist:
|
||||
pass
|
||||
try:
|
||||
Domain.objects.get(name=name)
|
||||
raise forms.ValidationError("Domain already exists")
|
||||
except Domain.DoesNotExist:
|
||||
pass
|
||||
|
||||
return name
|
||||
|
||||
|
@ -50,6 +57,9 @@ class NameserverForm(MntFormMixin, forms.ModelForm):
|
|||
|
||||
super(NameserverForm, self).__init__(*args, **kwargs)
|
||||
|
||||
instance = getattr(self, "instance", None)
|
||||
self._create = not (instance and instance.pk)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name'].lower().strip()
|
||||
if not name.endswith("."):
|
||||
|
@ -62,8 +72,14 @@ class NameserverForm(MntFormMixin, forms.ModelForm):
|
|||
|
||||
mnts = self._user.maintainer_set.all()
|
||||
domains = Domain.objects.filter(mnt_by__in=mnts)
|
||||
found = False
|
||||
for domain in domains:
|
||||
if domain.endswith(name)
|
||||
if domain.name == zone:
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise forms.ValidationError("This nameserver is not under a domain you control.")
|
||||
|
||||
return name
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-13 11:18
|
||||
# Generated by Django 1.10.5 on 2017-03-14 21:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
@ -35,7 +35,7 @@ class Migration(migrations.Migration):
|
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_modified', models.DateTimeField(auto_now_add=True)),
|
||||
('name', models.CharField(max_length=256)),
|
||||
('name', models.CharField(max_length=256, unique=True)),
|
||||
('glueIPv4', models.GenericIPAddressField(blank=True, null=True, protocol='IPv4')),
|
||||
('glueIPv6', models.GenericIPAddressField(blank=True, null=True, protocol='IPv6')),
|
||||
('admin_c', models.ManyToManyField(to='whoisdb.Contact')),
|
||||
|
@ -45,38 +45,16 @@ class Migration(migrations.Migration):
|
|||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NameserverDomainAssignment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveSmallIntegerField(default=0)),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.Domain')),
|
||||
('nameserver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.Nameserver')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NameserverReverseZoneAssignment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveSmallIntegerField()),
|
||||
('nameserver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.Nameserver')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReverseZone',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('address', models.GenericIPAddressField(db_index=True)),
|
||||
('netmask', models.PositiveIntegerField()),
|
||||
('nameservers', models.ManyToManyField(through='domains.NameserverReverseZoneAssignment', to='domains.Nameserver')),
|
||||
('nameservers', models.ManyToManyField(to='domains.Nameserver')),
|
||||
('parentNet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='whoisdb.InetNum')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='nameserverreversezoneassignment',
|
||||
name='reversezone',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.ReverseZone'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='nameservers',
|
||||
|
|
|
@ -6,6 +6,7 @@ from whoisdb.models import MntdObject, Contact, InetNum
|
|||
# generally allow domains for .dn to be created
|
||||
# allow owners of a subnet to create reverse dns record?
|
||||
|
||||
|
||||
class Nameserver(MntdObject):
|
||||
handleSuffix = "NS"
|
||||
# dns name
|
||||
|
@ -24,6 +25,7 @@ class Nameserver(MntdObject):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Domain(MntdObject):
|
||||
handle = None
|
||||
handleSuffix = "DOM"
|
||||
|
@ -38,18 +40,29 @@ class Domain(MntdObject):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def getNoDeleteReasons(self):
|
||||
reasons = []
|
||||
|
||||
nameservers = Nameserver.objects.filter(name__endswith="." + self.name)
|
||||
for ns in nameservers:
|
||||
reasons.append("Nameserver %s depends on this domain" % ns.name)
|
||||
|
||||
return reasons
|
||||
|
||||
#class NameserverDomainAssignment(models.Model):
|
||||
# domain = models.ForeignKey(Domain)
|
||||
# nameserver = models.ForeignKey(Nameserver)
|
||||
#
|
||||
# order = models.PositiveSmallIntegerField(default=0)
|
||||
|
||||
|
||||
class ReverseZone(models.Model):
|
||||
parentNet = models.ForeignKey(InetNum)
|
||||
address = models.GenericIPAddressField(db_index=True)
|
||||
netmask = models.PositiveIntegerField()
|
||||
|
||||
nameservers = models.ManyToManyField(Nameserver, through='NameserverReverseZoneAssignment')
|
||||
nameservers = models.ManyToManyField(Nameserver)
|
||||
|
||||
|
||||
#class NameserverReverseZoneAssignment(models.Model):
|
||||
# reversezone = models.ForeignKey(ReverseZone)
|
||||
|
|
|
@ -6,8 +6,12 @@ urlpatterns = [
|
|||
url(r'^$', domains_views.overview, name='overview'),
|
||||
|
||||
url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'),
|
||||
url(r'show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail, name='domain-show'),
|
||||
url(r'domain/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail.as_view(), name='domain-show'),
|
||||
url(r'domain/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainEdit.as_view(), name='domain-edit'),
|
||||
url(r'domain/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDelete.as_view(), name='domain-delete'),
|
||||
|
||||
url(r'nameserver/create/$', domains_views.NameserverCreate.as_view(), name='nameserver-create'),
|
||||
url(r'nameserver/show/(?P<pk>\d+)/$', domains_views.NameserverDetail, name='nameserver-show'),
|
||||
url(r'nameserver/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.NameserverDetail.as_view(), name='nameserver-show'),
|
||||
url(r'nameserver/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.NameserverEdit.as_view(), name='nameserver-edit'),
|
||||
url(r'nameserver/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.NameserverDelete.as_view(), name='nameserver-delete'),
|
||||
]
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
from django.shortcuts import render
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.generic import DetailView, CreateView, UpdateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from whoisdb.generic import MntGenericMixin
|
||||
from whoisdb.generic import MntGenericMixin, DeleteCheckView
|
||||
|
||||
from .models import Domain, Nameserver
|
||||
from .forms import DomainForm, NameserverForm
|
||||
|
||||
|
||||
@login_required
|
||||
def overview(request):
|
||||
mnts = request.user.maintainer_set.all()
|
||||
|
@ -18,6 +20,7 @@ def overview(request):
|
|||
|
||||
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers})
|
||||
|
||||
|
||||
class DomainCreate(LoginRequiredMixin, CreateView):
|
||||
template_name = "domains/obj_create.html"
|
||||
form_class = DomainForm
|
||||
|
@ -28,13 +31,33 @@ class DomainCreate(LoginRequiredMixin, CreateView):
|
|||
|
||||
return kwargs
|
||||
|
||||
|
||||
class DomainDetail(LoginRequiredMixin, DetailView):
|
||||
model = Domain
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
context_object_name = "domain"
|
||||
|
||||
|
||||
class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
|
||||
template_name = "domain"
|
||||
model = Domain
|
||||
form_class = DomainForm
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
template_name = "domains/obj_edit.html"
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
kwargs = super(DomainEdit, self).get_form_kwargs(*args, **kwargs)
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
|
||||
class DomainDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
|
||||
template_name = "domains/obj_delete.html"
|
||||
model = Domain
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
success_url = reverse_lazy("domains:overview")
|
||||
|
||||
|
||||
class NameserverCreate(LoginRequiredMixin, CreateView):
|
||||
|
@ -47,8 +70,30 @@ class NameserverCreate(LoginRequiredMixin, CreateView):
|
|||
|
||||
return kwargs
|
||||
|
||||
|
||||
class NameserverDetail(LoginRequiredMixin, DetailView):
|
||||
model = Nameserver
|
||||
#slug_field = "name"
|
||||
#slug_url_kwarg = "domain"
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
context_object_name = "nameserver"
|
||||
|
||||
|
||||
class NameserverEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
|
||||
model = Nameserver
|
||||
form_class = NameserverForm
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
template_name = "domains/obj_edit.html"
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
kwargs = super(NameserverEdit, self).get_form_kwargs(*args, **kwargs)
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
|
||||
class NameserverDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
|
||||
template_name = "domains/obj_delete.html"
|
||||
model = Nameserver
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
success_url = reverse_lazy("domains:overview")
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Header</div>
|
||||
<div class="panel-body">
|
||||
{{ domain.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Header</div>
|
||||
<div class="panel-body">
|
||||
{{ nameserver.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-{% if reasons %}danger{%else%}default{%endif%}">
|
||||
<div class="panel-heading">Header</div>
|
||||
<div class="panel-body">
|
||||
{% if reasons %}
|
||||
<p>
|
||||
You cannot delete this object, as other objects in the database depend on it!
|
||||
</p>
|
||||
<p>
|
||||
<ul>
|
||||
{% for reason in reasons %}
|
||||
<li>{{ reason }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ obj }}
|
||||
<form method="post" action="#">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-primary">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Header</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="#">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -8,15 +8,46 @@
|
|||
<div class="panel-body">
|
||||
<p>
|
||||
Your nameservers (<a href="{% url "domains:nameserver-create" %}">New nameserver</a>)
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Nameserver</th>
|
||||
<th>Glue IPv4</th>
|
||||
<th>Glue IPv6</th>
|
||||
<th>MNTs</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for nameserver in nameservers %}
|
||||
{{ nameserver }}<br />
|
||||
<tr>
|
||||
<td><a href="{% url "domains:nameserver-show" nameserver.name %}">{{ nameserver.name }}</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><a href="{% url "domains:nameserver-edit" nameserver.name %}">Edit</a> <a href="{% url "domains:nameserver-delete" nameserver.name %}">Delete</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
Your domains (<a href="{% url "domains:domain-create" %}">New domain</a>)
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Nameserver</th>
|
||||
<th>Glue IPv6</th>
|
||||
<th>MNTs</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td><a href="{% url "domains:domain-show" domain.name %}">{{ domain.name }}</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><a href="{% url "domains:domain-edit" domain.name %}">Edit</a> <a href="{% url "domains:domain-delete" domain.name %}">Delete</a></td>
|
||||
</tr>
|
||||
{{ domain }}<br />
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue