Procházet zdrojové kódy

Lots of work & RotBot networks forms and pages

tBKwtWS před 6 roky
rodič
revize
c5f25183a6

+ 2 - 4
.gitignore

@@ -1,15 +1,13 @@
-# Exclude virtual enviroment.
+# Virtual enviroment
 /bin/
 /include/
 /lib/
 /share/
 
-# django
+# Django
 __pycache__/
 db.sqlite3
 **/migrations/*.py
 
 # Node
 /node_modules/
-
-# semantic / fomantic

+ 1 - 1
website/core/templates/core/index.html

@@ -7,6 +7,6 @@
     <a href="{% url 'dancecalendar:index'  %}"><i class="calendar alternate icon"></i>Dance calendar</a><br />
   {% endif %}
   {% if 'rotbot.apps.RotbotConfig' in settings.INSTALLED_APPS %}
-    <a href=""><i class=" icon"></i>RotBot</a><br />
+    <a href="{% url 'rotbot:networks' %}"><i class="robot icon"></i>RotBot</a><br />
   {% endif %}
 {% endblock %}

+ 7 - 9
website/core/templates/core/login.html

@@ -1,12 +1,5 @@
 {% extends "base.html" %}
 {% block content %}
-  {% if next %}
-    {% if user.is_authenticated %}
-      <p>Your account doesn't have access to this page. To proceed, please login with an account that has access.</p>
-    {% else %}
-      <p>Please login to see this page.</p>
-    {% endif %}
-  {% endif %}
 <div class="ui compact inverted segment">
   <form class= "ui form" method="post" action="{% url 'core:login' %}">
     {% csrf_token %}
@@ -19,9 +12,14 @@
       {{ form.password }}
     </div>
     {% if form.errors %}
-      <div class="ui inverted orange message">Username and password didn't match.</div>
+      <div class="ui inverted red message">Username and password didn't match.</div>
+    {% endif %}
+    {% if next %}
+      {% if user.is_authenticated %}
+        <div class="ui inverted orange message">Your account doesn't have access to this page. To proceed, please login with an account that has access.</div>
+      {% endif %}
     {% endif %}
-    <button class="ui button" type="submit"><i class="door open icon"></i>Log in</button>
+    <button class="ui primary button" type="submit"><i class="door open icon"></i>Log in</button>
     <input type="hidden" name="next" value="{{ next }}">
   </form>
 </div>

+ 0 - 6
website/core/views.py

@@ -19,12 +19,6 @@ def login(request):
     user = authenticate(request, username=username, password=password)
     if user is not None:
         login(request, user)
-        # Redirect to a success page.
-        ...
-    else:
-        # Return an 'invalid login' error message.
-        ...
 
 def logout_view(request):
     logout(request)
-    # Redirect to a success page.

+ 3 - 1
website/knowledgebase/templates/knowledgebase/article.html

@@ -1,7 +1,9 @@
 {% extends "base.html" %}
 {% block content %}
   <h1>{{ article.title }}</h1>
-  <a href="{% url 'knowledgebase:edit-article' article_slug=article.slug %}" class="ui right floated button">Edit</a>
+  {% if perms.knowledgebase.change_article %}
+    <a href="{% url 'knowledgebase:edit-article' article_slug=article.slug %}" class="ui inverted right floated button">Edit</a>
+  {% endif %}
   {{ article.description }}
   <div class="segment">
   Category: {{ category }} | Created: {{ article.created }} {% if article.created != article.updated %}| Updated: {{ article.updated }} {% endif %}

+ 18 - 8
website/knowledgebase/templates/knowledgebase/edit_article.html

@@ -1,11 +1,21 @@
 {% extends "base.html" %}
 {% block content %}
-  <h1>{{ article.title }}</h1>
-  <button class="ui right floated button">Save</button>
-  {{ article.description }}
-  <div class="segment">
-  Category: {{ category }}
-  </div>
-  <div class="ui inverted divider"></div>
-  {% autoescape off %}{{ article.content }}{% endautoescape %}
+  <form class= "ui form" method="post" action="{% url 'knowledgebase:save-article' article.slug %}">
+    {% csrf_token %}
+    <h1>
+      Title: <input class="required field" type="text" name="title" value="{{ article.title }}"></input>
+    </h1>
+    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
+    <button class="ui right floated button">Save</button>
+    Description: <textarea class="required field" type="text" name="description" rows="2">{{ article.description }}</textarea>
+    <div class="segment">
+    Category: <select class="required field" type="dropdown" name="category">
+      <option value="nx" {% if article.category == 'nx' %}selected{% endif %}>Nginx</option>
+      <option value="ot" {% if article.category == 'ot' %}selected{% endif %}>Other</option>
+    </select>
+    </div>
+    <div class="ui inverted divider"></div>
+    <textarea class="required field" name="content" >{{ article.content }}</textarea>
+    {% autoescape off %}{{ article.content }}{% endautoescape %}
+  </form>
 {% endblock content %}

+ 1 - 1
website/knowledgebase/templates/knowledgebase/index.html

@@ -5,7 +5,7 @@
       {% for article in latest_article_list %}
         <div class="item" onclick="location.href='{% url 'knowledgebase:article' article.slug %}';">
           <div class="right floated content">
-            <div class="ui inverted basic tiny black right ribbon label"><i class="{{ article.icon }} icon"></i> {{ article.category }} </div>
+            <div class="ui inverted basic tiny black right ribbon label"><i class="{{ article.icon }} icon"></i> {{ article.verbose_category }} </div>
           </div>
           <a class ="header" href="{% url 'knowledgebase:article' article.slug %}">{{ article.title }}</a>
           <div class="description">{{ article.description }}</div>

+ 1 - 0
website/knowledgebase/urls.py

@@ -7,4 +7,5 @@ urlpatterns = [
     path('', views.index, name='index'),
     path('<str:article_slug>/', views.article, name='article'),
     path('<str:article_slug>/edit', views.edit_article, name='edit-article'),
+    path('<str:article_slug>/save', views.save_article, name='save-article'),
 ]

+ 14 - 8
website/knowledgebase/views.py

@@ -1,6 +1,6 @@
-#from django.shortcuts import render
-from django.http import Http404
+from django.http import HttpRequest, HttpResponseRedirect
 from django.shortcuts import render, get_object_or_404
+from django.urls import reverse
 
 from .models import Article
 
@@ -12,7 +12,7 @@ def index(request):
         'parent_title': 'Knowledgebase',
         'parent_url': 'knowledgebase:index',
         'parent_icon': 'newspaper outline',
-        'title': 'Knowledgebase',
+        'title': 'Index',
         'icon': 'list',
         'description': 'Index of knowledgebase articles.',
         'keywords': 'knowledgebase, articles, index',
@@ -46,18 +46,24 @@ def edit_article(request, article_slug):
         'parent_icon': 'newspaper outline',
         'title': 'Edit ' + article.title,
         'icon': 'file alternate outline',
-        'description': article.description,
-        'keywords': article.keywords,
         'article': article,
-        'category': article.category,
     }
     return render(request, 'knowledgebase/edit_article.html', context)
 
+def save_article(request, article_slug):
+    article = get_object_or_404(Article, slug=article_slug)
+    article.title = request.POST['title']
+    article.description = request.POST['description']
+    article.category = request.POST['category']
+    article.content = request.POST['content']
+    article.save()
+    return HttpResponseRedirect(reverse('knowledgebase:article', args=(article.slug,)))
+
 def article_decoration(article):
     if article.category == 'ot':
-        article.category = 'Other'
+        article.verbose_category = 'Other'
         article.icon = 'archive'
     else:
-        article.category = 'Unkown'
+        article.verbose_category = 'Unkown'
         article.icon = 'question'
     return article

+ 4 - 1
website/rotbot/admin.py

@@ -1,3 +1,6 @@
 from django.contrib import admin
 
-# Register your models here.
+from .models import Network, Host
+
+admin.site.register(Network)
+admin.site.register(Host)

+ 40 - 0
website/rotbot/forms.py

@@ -0,0 +1,40 @@
+from django import forms
+from django.forms import ModelForm, CharField, SlugField
+from django.core.validators import validate_unicode_slug
+
+from .models import Network, Host
+
+class NetworkForm(ModelForm):
+    class Meta:
+        model=Network
+        #fields = ['name', 'slug', 'nickname', 'password', 'username', 'home_channel', 'command_character', 'help_character']
+        fields='__all__'
+        #exclude = ['slug'] #Does not need "fields = '__all__'".
+        labels={
+            'name': '<i class="sitemap icon"></i>Name',
+            'slug': '<i class="linkify icon"></i>Slug',
+            'nickname': '<i class="id badge icon"></i>Nickname',
+            'username': '<i class="id card icon"></i>Username',
+            'password': '<i class="privacy icon"></i>Password',
+            'home_channel': '<i class="hashtag icon"></i>Home channel',
+            'command_character': '<i class="terminal icon"></i>Command character',
+            'help_character': '<i class="help icon"></i>Help character',
+        }
+        widgets={
+            'name': forms.TextInput(attrs={'autocomplete': 'on'}),
+            'nickname': forms.TextInput(attrs={'autocomplete': 'on'}),
+            'username': forms.TextInput(attrs={'autocomplete': 'on'}),
+        }
+
+class HostForm(ModelForm):
+    class Meta:
+        model=Host
+        exclude=['network']
+        labels={
+            'address': '<i class="server icon"></i>Address',
+            'port': '<i class="dungeon icon"></i>Port',
+            'ssl': '<i class="lock icon"></i>Use SSL',
+        }
+        widgets={
+            'address': forms.TextInput(attrs={'autocomplete': 'on'}),
+        }

+ 48 - 4
website/rotbot/models.py

@@ -1,48 +1,92 @@
 from django.db import models
+from django.core.validators import validate_unicode_slug, MaxLengthValidator, MaxValueValidator, URLValidator
 
 # Create your models here.
 class Network(models.Model):
     name = models.CharField(
         max_length=40,
         unique=True,
+        validators=[MaxLengthValidator(40)],
+    )
+    slug = models.SlugField(
+        db_index=True,
+        unique=True,
+        validators=[validate_unicode_slug],
     )
     nickname = models.CharField(
         max_length=31,
         default='RotBot',
+        validators=[MaxLengthValidator(31)],
     )
     username = models.CharField(
         max_length=31,
         default='pyRot',
+        validators=[MaxLengthValidator(31)],
     )
     password = models.CharField(
         null=True,
         blank=True,
         max_length=31,
+        validators=[MaxLengthValidator(31)],
     )
     home_channel = models.CharField(
         max_length=64,
         default='#RotBot',
+        validators=[MaxLengthValidator(64)],
     )
     command_character = models.CharField(
         max_length=1,
-        default='!'
+        default='!',
+        validators=[MaxLengthValidator(1)],
     )
     help_character = models.CharField(
         max_length=1,
-        default='@'
+        default='@',
+        validators=[MaxLengthValidator(1)],
+    )
+    enabled = models.BooleanField(
+        default=True,
     )
 
+    class Meta:
+        ordering = ['name']
+
+    def get_absolute_url(self):
+        from django.urls import reverse
+        return reverse('rotbot.views.network', args=[str(self.id)])
+        # Try this in the template: <a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
+
+
+    def __str__(self):
+        return self.name
+
 class Host(models.Model):
     network = models.ForeignKey(
         'Network',
+        on_delete=models.CASCADE,
+        related_name="host",
+        related_query_name="hosts",
     )
     address = models.CharField(
-        max_length=60,
+        max_length=200,
         unique=True,
+        validators=[MaxLengthValidator(200)],
     )
     port = models.PositiveSmallIntegerField(
-         =
+        default = 6697,
+        validators=[MaxValueValidator(65535)],
     )
     ssl = models.BooleanField(
         default=True,
     )
+
+    class Meta:
+        order_with_respect_to = 'network'
+        unique_together = ['address', 'port']
+
+    @property
+    def connect_string(self):
+        return '%s:%s' % (self.address, self.port)
+
+    def __str__(self):
+        return '%s:%s' % (self.address, self.port)

+ 7 - 0
website/rotbot/templates/rotbot/add_network.html

@@ -0,0 +1,7 @@
+{% extends "rotbot/network_form.html" %}
+{% block formtag %}{% url 'rotbot:add_network' %}{% endblock formtag %}
+{% block buttons %}
+  <a class="ui right floated inverted basic negative button" href="{% url 'rotbot:networks' %}">
+    <i class="hand point left icon"></i>Back
+  </a>
+{% endblock buttons %}

+ 8 - 0
website/rotbot/templates/rotbot/edit_hosts.html

@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+{% block content %}
+<form method="post" action="{% url 'rotbot:edit_hosts' network_slug %}">
+  {% csrf_token %}
+  {{ formset }}
+  <button class="ui right floated inverted positive button" type="submit" value="Submit"><i class="save icon"></i>Save</button>
+</form>
+{% endblock content %}

+ 10 - 0
website/rotbot/templates/rotbot/edit_network.html

@@ -0,0 +1,10 @@
+{% extends "rotbot/network_form.html" %}
+{% block formtag %}{% url 'rotbot:edit_network' network_slug %}{% endblock formtag %}
+{% block buttons %}
+<a class="ui right floated inverted negative button" href="{% url 'rotbot:delete_network' network_slug %}">
+  <i class="trash alternate icon"></i>Delete
+</a>
+<a class="ui right floated inverted basic negative button" href="{% url 'rotbot:network' network_slug %}">
+  <i class="hand point left icon"></i>Back
+</a>
+{% endblock buttons %}

+ 98 - 0
website/rotbot/templates/rotbot/network.html

@@ -0,0 +1,98 @@
+{% extends "base.html" %}
+{% block content %}
+  <div class="ui inverted card">
+    <div class="content">
+      <div class="header" title="Network">
+        <i class="sitemap icon"></i>
+        {{ network.name }}
+        {% if network.enabled %}
+          <i class="power off icon" title="Enabled"></i>
+        {% else %}
+          <i class="minus circle" title="Disabled"></i>
+        {% endif %}
+      </div>
+      <div class="ui list">
+        {% for host in network.host.all %}
+          <div class="item" title="Server">
+            <i class="server icon"></i>
+            <div class="content">{{ host }}
+            {% if host.ssl %}
+              <i class="green lock icon" title="Encrypted connection"></i>
+            {% endif %}
+            </div>
+          </div>
+        {% endfor %}
+      </div>
+    </div>
+    <div class="extra content">
+      <div class="ui list">
+        <div class="item" title="Home channel">
+          <i class="hashtag icon"></i>
+          <div class="content">
+              {{ network.home_channel }}
+          </div>
+        </div>
+        <div class="item" title="Nickname">
+          <i class="id badge icon"></i>
+          <div class="content">
+            {{ network.nickname }}
+            {% if network.password %}
+              <i class="green user lock icon" title="NickServ password saved"></i>
+            {% endif %}
+          </div>
+        </div>
+        <div class="item" title="Username">
+          <i class="id card icon"></i>
+          <div class="content">
+            {{ network.username }}
+          </div>
+        </div>
+      </div>
+    </div>
+    {% if perms.rotbot.change_network %}
+      <div class="ui top right attached black label">
+        <a href="{% url 'rotbot:edit_network' network_slug=network.slug %}" class="ui inverted button">Edit</a>
+      </div>
+    {% endif %}
+    <div class="ui inverted bottom right attached basic label">
+      <div class="ui inverted divided horizontal list">
+          <div class="item" title="Command character">
+              <i class="terminal icon"></i>
+              {{ network.command_character }}
+          </div>
+          <div class="item" title="Help character">
+              <i class="help icon"></i>
+              {{ network.help_character }}
+          </div>
+      </div>
+    </div>
+  </div>
+{% endblock content %}
+
+
+
+
+
+<section class="ui stackable cards">
+    <figure class="PhotoCard ui card">
+        <img src="http://via.placeholder.com/300x250" class="ui fluid image">
+        <figcaption class="content">
+            Cool pics
+        </figcaption>
+    </figure>
+</section>
+<script>
+    function deleteModal() {
+        $('.ui.basic.modal').each(function() {
+            $(this).remove();
+        });
+    }
+    $('.PhotoCard').click(function() {
+        // Delete any modals hanging around
+        deleteModal();
+        var image = $(this).children('img').attr('src');
+        $('body').append('<div class="ui basic modal"><div class="content"><img src="'+image+'" width="100%" /></div></div>');
+        $('.ui.basic.modal')
+            .modal('show');
+    })
+</script>

+ 151 - 0
website/rotbot/templates/rotbot/network_form.html

@@ -0,0 +1,151 @@
+{% extends "base.html" %}
+{% block content %}
+  <form class= "ui form" method="post" action="{% block formtag %}{% endblock formtag %}">
+  {% csrf_token %}
+  {{ form.non_field_errors }}
+  {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
+  <div class="two fields">
+    <div class="required field{% if form.name.errors %} error{% endif %}">
+      <label for="{{ form.name.id_for_label }}">{{ form.name.label|safe }}</label>
+      {{ form.name }}
+      {% if form.name.errors %}
+        <div class="ui inverted red message">
+          {{ form.name.errors }}
+        </div>
+      {% endif %}
+    </div>
+    <div class="required field">
+      <label for="{{ form.slug.id_for_label }}">{{ form.slug.label|safe }}</label>
+      {{ form.slug }}
+      {% if form.slug.errors %}
+        <div class="ui inverted red message">
+          {{ form.slug.errors }}
+        </div>
+      {% endif %}
+    </div>
+  </div>
+  {{ hostformset.management_form }}
+  {% for hostform in hostformset %}
+    {{ hostform.non_field_errors }}
+    {% for field in hostform.hidden_fields %}{{ field }}{% endfor %}
+    <div class="four fields">
+      <div class="required field">
+        <label for="{{ hostform.address.id_for_label }}">{{ hostform.address.label|safe }}</label>
+        {{ hostform.address }}
+        {% if hostform.address.errors %}
+          <div class="ui inverted red message">
+            {{ hostform.address.errors }}
+          </div>
+        {% endif %}
+      </div>
+      <div class="required field">
+        <label for="{{ hostform.port.id_for_label }}">{{ hostform.port.label|safe }}</label>
+        {{ hostform.port }}
+        {% if hostform.port.errors %}
+          <div class="ui inverted red message">
+            {{ hostform.port.errors }}
+          </div>
+        {% endif %}
+      </div>
+      <div class="field">
+        <div class="ui inverted toggle checkbox">
+          {{ hostform.ssl }}
+          <label for="{{ hostform.ssl.id_for_label }}">{{ hostform.ssl.label|safe }}</label>
+          {% if hostform.port.errors %}
+            <div class="ui inverted red message">
+              {{ hostform.port.errors }}
+            </div>
+          {% endif %}
+
+        </div>
+      </div>
+      <div class="field">
+        <div class="ui inverted checkbox">
+          {{ hostform.DELETE }}
+          {% if hostform.instance.pk %}
+            <label for="{{ hostform.DELETE.id_for_label }}">{{ hostform.DELETE.label }}</label>
+          {% else %}
+            <label for="{{ hostform.DELETE.id_for_label }}">Clear</label>
+          {% endif %}
+          {% if hostform.DELETE.errors %}
+            <div class="ui inverted red message">
+              {{ hostform.DELETE.errors }}
+            </div>
+          {% endif %}
+        </div>
+      </div>
+    </div>
+  {% endfor %}
+
+  <div class="three fields">
+    <div class="required field">
+      <label for="{{ form.nickname.id_for_label }}">{{ form.nickname.label|safe }}</label>
+      {{ form.nickname }}
+      {% if form.nickname.errors %}
+        <div class="ui inverted red message">
+          {{ form.nickname.errors }}
+        </div>
+      {% endif %}
+    </div>
+    <div class="field">
+      <label for="{{ form.password.id_for_label }}">{{ form.password.label|safe }}</label>
+      {{ form.password }}
+      {% if form.password.errors %}
+        <div class="ui inverted red message">
+          {{ form.password.errors }}
+        </div>
+      {% endif %}
+    </div>
+    <div class="required field">
+      <label for="{{ form.username.id_for_label }}">{{ form.username.label|safe }}</label>
+      {{ form.username }}
+      {% if form.username.errors %}
+        <div class="ui inverted red message">
+          {{ form.username.errors }}
+        </div>
+      {% endif %}
+    </div>
+  </div>
+      <div class="required field">
+      <label for="{{ form.home_channel.id_for_label }}">{{ form.home_channel.label|safe }}</label>
+      {{ form.home_channel }}
+      {% if form.home_channel.errors %}
+        <div class="ui inverted red message">
+          {{ form.home_channel.errors }}
+        </div>
+      {% endif %}
+    </div>
+  <div class="two fields">
+    <div class="required field">
+      <label for="{{ form.command_character.id_for_label }}">{{ form.command_character.label|safe }}</label>
+      {{ form.command_character }}
+      {% if form.command_character.errors %}
+        <div class="ui inverted red message">
+          {{ form.command_character.errors }}
+        </div>
+      {% endif %}
+    </div>
+    <div class="required field">
+      <label for="{{ form.help_character.id_for_label }}">{{ form.help_character.label|safe }}</label>
+      {{ form.help_character }}
+      {% if form.help_character.errors %}
+        <div class="ui inverted red message">
+          {{ form.help_character.errors }}
+        </div>
+      {% endif %}
+    </div>
+  </div>
+  <div class="ui inverted toggle checkbox">
+    {{ form.enabled }}
+    <label for="{{ form.enabled.id_for_label }}">{{ form.enabled.label|safe }}</label>
+    {% if form.enabled.errors %}
+      <div class="ui inverted red message">
+        {{ form.enabled.errors }}
+      </div>
+    {% endif %}
+  </div>
+  <button class="ui right floated inverted positive button" type="submit" value="Submit"><i class="save icon"></i>Save</button>
+  {% block buttons %}{% endblock buttons %}
+  </form>
+
+{% endblock %}

+ 29 - 0
website/rotbot/templates/rotbot/networks.html

@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+{% block content %}
+  {% if perms.rotbot.add_network %}
+    <a href="{% url 'rotbot:add_network' %}" class="ui inverted right floated button">Add</a>
+  {% endif %}
+  {% if network_list %}
+    <div class="ui inverted relaxed divided selection list">
+      {% for network in network_list.all %}
+        <div class="item" onclick="location.href='{% url 'rotbot:network' network.slug %}';">
+          <a class ="header" href="{% url 'rotbot:network' network.slug %}">{{ network.name }}</a>
+        </div>
+      {% endfor %}
+    </div>
+  {% else %}
+    <p>No networks available.</p>
+  {% endif %}
+{% endblock %}
+
+{#
+
+
+
+
+{% if athlete_list|length > 1 %}
+   Team: {% for athlete in athlete_list %} ... {% endfor %}
+{% else %}
+   Athlete: {{ athlete_list.0.name }}
+{% endif %}
+#}

+ 13 - 0
website/rotbot/urls.py

@@ -0,0 +1,13 @@
+from django.urls import path
+
+from . import views
+
+app_name = 'rotbot'
+urlpatterns = [
+    path('networks/', views.IndexView.as_view(), name='networks'),
+    path('network/add', views.add_network, name='add_network'),
+    path('network/<str:network_slug>/', views.network, name='network'),
+    path('network/<str:network_slug>/edit/', views.edit_network, name='edit_network'),
+    path('network/<str:network_slug>/delete/', views.delete_network, name='delete_network'),
+    #path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
+]

+ 114 - 2
website/rotbot/views.py

@@ -1,3 +1,115 @@
-from django.shortcuts import render
+from django.shortcuts import render, get_object_or_404
+from django.http import HttpResponseRedirect
+from django.urls import reverse
+from django.views import generic
+from django.contrib.auth.decorators import login_required, permission_required
+from django.forms import modelformset_factory, inlineformset_factory
 
-# Create your views here.
+from website.settings import APPLICATION_NAME
+from .models import Network, Host
+from .forms import NetworkForm, HostForm
+
+def default_keywords(additional_keywords=None):
+    default_keywords = 'RotBot, robot, bot, irc, irc bot, irc robot, '
+    if additional_keywords:
+        additional_keywords = ', '.join(map(str, additional_keywords))
+        return (default_keywords + additional_keywords)
+    return (default_keywords)
+
+
+class IndexView(generic.ListView):
+    template_name = 'rotbot/networks.html'
+    context_object_name = 'network_list'
+
+    extra_context = {
+        'title': 'RotBot',
+        'icon': 'robot',
+        'description': 'Index of RotBot, the IRC robot.',
+        'keywords': default_keywords() + 'index',
+    }
+
+    def get_queryset(self):
+        #return Question.objects.order_by('-pub_date')[:5]
+        return Network.objects
+
+
+def network(request, network_slug):
+    network = get_object_or_404(Network, slug=network_slug)
+    context = {
+        'parent_title': 'Networks',
+        'parent_url': 'rotbot:networks',
+        'parent_icon': 'robot',
+        'title': network.name,
+        'icon': 'sitemap',
+        'description': 'Details of ' + network.name,
+        'keywords': default_keywords() + 'network.name, display, details',
+        'network': network,
+    }
+    return render(request, 'rotbot/network.html', context)
+
+
+@login_required
+@permission_required('rotbot.change_network', raise_exception=True)
+def edit_network(request, network_slug):
+    network = get_object_or_404(Network, slug=network_slug)
+    title = network.name
+    HostFormSet = inlineformset_factory(Network, Host, form=HostForm)
+    if request.method == 'POST':
+        form = NetworkForm(request.POST, instance=network)
+        formset = HostFormSet(request.POST, instance=network)
+        if form.is_valid() and formset.is_valid():
+            form.save()
+            formset.save()
+            return HttpResponseRedirect(reverse('rotbot:network', args=(network.slug,)))
+    else:
+        form = NetworkForm(instance=network)
+        formset = HostFormSet(instance=network)
+    context = {
+        'parent_title': 'Networks',
+        'parent_url': 'rotbot:networks',
+        'parent_icon': 'robot',
+        'title': title,
+        'icon': 'sitemap',
+        'description': 'Edit ' + title,
+        'keywords': default_keywords((title, 'edit')),
+        'network_slug': network_slug,
+        'form': form,
+        'hostformset': formset,
+    }
+    return render(request, 'rotbot/edit_network.html', context)
+
+@login_required
+@permission_required('rotbot.add_network', raise_exception=True)
+def add_network(request):
+    HostFormSet = inlineformset_factory(Network, Host, form=HostForm)
+    if request.method == 'POST':
+        form = NetworkForm(request.POST)
+        if form.is_valid():
+            network = form.save()
+            formset = HostFormSet(request.POST, instance=network)
+            if formset.is_valid():
+                formset.save()
+                return HttpResponseRedirect(reverse('rotbot:network', args=(form.cleaned_data['slug'],)))
+    else:   # Not a POST request.
+        form = NetworkForm()
+        formset = HostFormSet(queryset=Host.objects.none())
+    context = {
+        'parent_title': 'Networks',
+        'parent_url': 'rotbot:networks',
+        'parent_icon': 'robot',
+        'title': 'Add network',
+        'icon': 'sitemap',
+        'description': 'Add new irc network to ' + APPLICATION_NAME,
+        'keywords': default_keywords() + 'add, add network',
+        'form': form,
+        'hostformset': formset,
+    }
+    return render(request, 'rotbot/add_network.html', context)
+
+
+@login_required
+@permission_required('rotbot.change_network', raise_exception=True)
+def delete_network(request, network_slug):
+    network = get_object_or_404(Network, slug=network_slug)
+    network.delete()
+    return HttpResponseRedirect(reverse('rotbot:networks'))

+ 6 - 4
website/templates/base.html

@@ -13,7 +13,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <style type="text/css">
       .main.container {
-        padding-top: 7em;
+        padding-top: 5em;
       }
     </style>
     <script src="{% static "jquery@3.3.1/dist/jquery.min.js" %}"></script>
@@ -28,13 +28,15 @@
         {% if parent_title %}
           <a class="item" href="{% url parent_url %}"><i class="{{ parent_icon }} icon"></i>{{ parent_title }}</a>
         {% endif %}
-        <a class="active item" href="{{ request.path }}"><i class="{{ icon }} icon"></i>{{ title }}</a>
+        <a class="active item" href="{{ request.path }}">
+          <i class="{{ icon }} icon"></i>
+          {{ title }}</a>
       {% endif %}
       <div class="right menu">
         {% if user.is_authenticated %}
-          <a class="ui item" href="{% url 'core:logout' %}"><i class="sign out alternate icon"></i>Log out</a>
+          <a class="item" href="{% url 'core:logout' %}?next={{ request.path }}"><i class="sign out alternate icon"></i>Log out</a>
           {% else %}
-          <a class="ui item" href="{% url 'core:login' %}"><i class="sign in alternate icon"></i>Log in</a>
+          <a class="item" href="{% url 'core:login' %}?next={{ request.path }}"><i class="sign in alternate icon"></i>Log in</a>
           {% endif %}
       </div>
     </nav>

+ 2 - 0
website/website/settings.py

@@ -41,6 +41,7 @@ INSTALLED_APPS = [
     'core.apps.CoreConfig',
     'knowledgebase.apps.KnowledgebaseConfig',
     'dancecalendar.apps.DancecalendarConfig',
+    'rotbot.apps.RotbotConfig',
 ]
 
 MIDDLEWARE = [
@@ -131,6 +132,7 @@ STATICFILES_DIRS = [
 
 LOGIN_REDIRECT_URL = '/'
 LOGOUT_REDIRECT_URL = '/'
+LOGIN_URL = '/core/login'
 
 
 ## Settings export