Add services page.
authorRadek Czajka <rczajka@rczajka.pl>
Fri, 20 Aug 2021 13:50:39 +0000 (15:50 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Fri, 20 Aug 2021 13:50:39 +0000 (15:50 +0200)
17 files changed:
requirements.txt
src/accounts/templates/account/base.html
src/accounts/templates/account/profile.html
src/accounts/templates/registration/password_change_form.html
src/cas/settings.py
src/cas/static/css/main.css
src/cas/templates/base.html
src/cas/urls.py
src/services/migrations/0003_auto_20210820_1035.py [new file with mode: 0644]
src/services/models.py
src/services/templates/services/base.html [new file with mode: 0644]
src/services/templates/services/service_detail.html [new file with mode: 0644]
src/services/templates/services/service_list.html [new file with mode: 0644]
src/services/urls.py
src/services/views.py
src/ssh_keys/templates/ssh_keys/sshkey_add.html
src/ssh_keys/templates/ssh_keys/sshkey_list.html

index d5ed1f4..4a8db19 100644 (file)
@@ -1,5 +1,5 @@
-Django==3.1.7
+Django==3.2.6
 django-gravatar2==1.4.4
 django-oidc-provider2==0.8.3
 
--e git+https://github.com/fnp/django-cas-provider.git@53585f7615b3a647d1badda1a2ebd7f5ec81f607#egg=django-cas-provider
+-e git+https://github.com/fnp/django-cas-provider.git@122f2f5678cfe723a44c140206da2cb9117b46ee#egg=django-cas-provider
index 0b7cd3c..8e9245e 100644 (file)
@@ -9,6 +9,7 @@
          alt="Gravatar"
          title="{% trans 'Change at Gravatar.com' %}">
     {% block menu %}
+      <p><a {% if menu == 'services' %}class="active"{% endif %} href="{% url 'services' %}">{% trans "Services" %}</a></p>
       <p><a {% if menu == 'profile' %}class="active"{% endif %} href="{% url 'accounts_profile' %}">{% trans "Your profile" %}</a></p>
       <p><a {% if menu == 'password' %}class="active"{% endif %} href="{% url 'password_change' %}">{% trans "Password change" %}</a></p>
       {% use_email as use_email %}
 
   <div id="details">
     {% if messages %}
-      <section id="messages">
-        {% for message in messages %}<p>{{ message }}</p>{% endfor %}
-      </section>
+        {% for message in messages %}
+          <div class="alert alert-primary">
+            {{ message }}
+          </div>
+        {% endfor %}
     {% endif %}
-      
+
     {% block accounts-content %}
     {% endblock %}
   </div>
index 18bada0..9dff681 100644 (file)
@@ -17,6 +17,6 @@
   <form method="post">
     {% csrf_token %}
     {{ form.as_p }}
-    <p><button type="submit">{% trans "Change profile" %}</button></p>
+    <p><button type="submit" class="btn btn-primary">{% trans "Change profile" %}</button></p>
   </form>
 {% endblock %}
index c968ee5..760d1c0 100644 (file)
@@ -31,7 +31,7 @@
     {{ form.as_p }}
 
     <p>
-      <input type="submit" value="{% trans 'Change my password' %}">
+      <input type="submit" value="{% trans 'Change my password' %}" class="btn btn-primary">
     </p>
 
   </form>
index ab10159..346e6f4 100644 (file)
@@ -20,6 +20,8 @@ DATABASES = {
     }
 }
 
+DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+
 # Local time zone for this installation. Choices can be found here:
 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
 # although not all choices may be available on all operating systems.
index 1329ac0..0c5cda4 100644 (file)
@@ -132,6 +132,7 @@ footer, #content_push {
 
 .menu img {
     box-shadow: 0 0 .2em black;
+    margin-bottom: 1em;
 }
 
 code.key {
@@ -142,3 +143,30 @@ code.key {
     display: block;
     color: #666;
 }
+
+
+.service-list {
+    display: flex;
+    gap: 10px;
+    flex-wrap: wrap;
+}
+a.service {
+    width: 130px;
+    height: 80px;
+    background: white;
+    box-shadow:  3px 3px 5px #aaa;
+    padding: 10px;
+    position: relative;
+    display: flex;
+    color: black;
+    align-items: center;
+    justify-content: center;
+}
+a.service:hover {
+    text-decoration: none;
+    background: orange;
+}
+.service-icon {
+    max-width: 100%;
+    max-height: 100%;
+}
index 570e404..46ca6a2 100644 (file)
@@ -4,6 +4,7 @@
   <head>
     <meta charset="utf-8" />
     <title>{% block title %}Logowanie{% endblock %} | Fundacja Nowoczesna Polska</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
     <link rel="stylesheet" href="{% static 'css/main.css' %}"/>
     <meta name="viewport" content="width=device-width, initial-scale=1">
     {% block extrahead %}
index ce01eb0..1c90a76 100644 (file)
@@ -28,9 +28,8 @@ urlpatterns = [
 
 
 if settings.DEBUG:
-    from django.views.static import serve
-    urlpatterns += [
-        path('media/<path>', serve, {
-            'document_root': settings.MEDIA_ROOT,
-        }),
-   ]
+    from django.conf.urls.static import static
+    urlpatterns += static(
+        settings.MEDIA_URL,
+        document_root=settings.MEDIA_ROOT
+    )
diff --git a/src/services/migrations/0003_auto_20210820_1035.py b/src/services/migrations/0003_auto_20210820_1035.py
new file mode 100644 (file)
index 0000000..5d40936
--- /dev/null
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.6 on 2021-08-20 10:35
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('services', '0002_auto_20190330_2220'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='service',
+            name='description',
+            field=models.TextField(blank=True, verbose_name='description'),
+        ),
+        migrations.AddField(
+            model_name='service',
+            name='for_all',
+            field=models.BooleanField(default=False, verbose_name='for all'),
+        ),
+        migrations.AddField(
+            model_name='service',
+            name='icon',
+            field=models.FileField(blank=True, upload_to='service/icon', verbose_name='icon'),
+        ),
+    ]
index c7a5d97..3261257 100644 (file)
@@ -2,6 +2,7 @@ import secrets
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.db import models
+from django.urls import reverse
 from django.utils.translation import ugettext_lazy as _
 
 
@@ -10,6 +11,11 @@ class Service(models.Model):
     url = models.URLField(_('URL'), blank=True)
     key = models.CharField(_('key'), max_length=255, blank=True)
     uses_ssh = models.BooleanField(_('uses SSH'), default=False)
+
+    for_all = models.BooleanField(_('for all'), default=False)
+    description = models.TextField(_('description'), blank=True)
+    icon = models.FileField(_('icon'), blank=True, upload_to='service/icon')
+
     groups = models.ManyToManyField('auth.Group', verbose_name=_('groups'), blank=True)
     users = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_('users'), blank=True)
 
@@ -21,6 +27,9 @@ class Service(models.Model):
     def __str__(self):
         return self.name
 
+    def get_absolute_url(self):
+        return reverse('service_detail', args=[self.pk])
+    
     def save(self, *args, **kwargs):
         if not self.key:
             self.key = secrets.token_urlsafe()
@@ -34,7 +43,9 @@ class Service(models.Model):
 
     @classmethod
     def for_user(cls, user):
-        return cls.objects.filter(models.Q(users=user) | models.Q(groups__user=user))
+        return cls.objects.filter(
+            models.Q(for_all=True) | models.Q(users=user) | models.Q(groups__user=user)
+        )
 
 
 class Hook(models.Model):
diff --git a/src/services/templates/services/base.html b/src/services/templates/services/base.html
new file mode 100644 (file)
index 0000000..f576af2
--- /dev/null
@@ -0,0 +1,7 @@
+{% extends "account/base.html" %}
+
+{% block menu %}
+  {% with menu="services" %}
+    {{ block.super }}
+  {% endwith %}
+{% endblock %}
diff --git a/src/services/templates/services/service_detail.html b/src/services/templates/services/service_detail.html
new file mode 100644 (file)
index 0000000..8cbea5e
--- /dev/null
@@ -0,0 +1,29 @@
+{% extends "services/base.html" %}
+{% load i18n %}
+
+
+{% block accounts-content %}
+  <h1>{% translate "Serwis" %}: {{ service.name }}</h1>
+
+  {% if service.url %}
+    {{ service.url }}
+  {% endif %}
+
+  {% if service.uses_ssh %}
+    {% url 'ssh_keys' as url_ssh_keys %}
+    {% if request.user.sshkey_set.exists %}
+      {% blocktrans %}
+        This service will use your <a href="{{ url_ssh_keys}}">SSH keys.</a>
+      {% endblocktrans %}
+    {% else %}
+      <div class="alert alert-warning">
+        {% blocktrans %}
+          This service uses SSH keys.
+          You can add one in the <a href="{{ url_ssh_keys}}">SSH keys</a> section.
+        {% endblocktrans %}
+      </div>
+    {% endif %}
+  {% endif %}
+
+  {{ service.description|linebreaks }}
+{% endblock %}
diff --git a/src/services/templates/services/service_list.html b/src/services/templates/services/service_list.html
new file mode 100644 (file)
index 0000000..b925ee1
--- /dev/null
@@ -0,0 +1,21 @@
+{% extends 'services/base.html' %}
+{% load i18n %}
+
+{% block accounts-content %}
+  <h1>{% translate "Services" %}</h1>
+
+  <div class="service-list">
+    {% for service in object_list %}
+      <a class="service" href="{% if service.url %}{{ service.url }}{% else %}{% url 'service_detail' service.pk %}{% endif %}" title="{{ service.name }}">
+        {% if service.icon %}
+          <img class="service-icon" alt="{{ service.name }}" src="{{ service.icon.url }}">
+        {% else %}
+          <span class="service-name">
+            {{ service.name }}
+          </span>
+        {% endif %}
+      </a>
+    {% endfor %}
+  </div>
+
+{% endblock %}
index fc34dcc..bc7f10e 100644 (file)
@@ -3,5 +3,7 @@ from . import views
 
 
 urlpatterns = [
+    path('', views.ServicesView.as_view(), name='services'),
+    path('<int:pk>/', views.ServiceDetail.as_view(), name='service_detail'),
     path('<int:pk>/ssh/authorized_keys', views.SshAuthorizedKeysView.as_view()),
 ]
index f599592..f96dd0b 100644 (file)
@@ -1,4 +1,4 @@
-from django.views.generic import DetailView
+from django.views.generic import DetailView, ListView
 from .models import Service
 
 
@@ -13,3 +13,12 @@ class SshAuthorizedKeysView(DetailView):
             obj = None
         return obj
 
+
+class ServicesView(ListView):
+    def get_queryset(self):
+        return Service.for_user(self.request.user)
+
+
+class ServiceDetail(DetailView):
+    def get_queryset(self):
+        return Service.for_user(self.request.user)
index 1b67d03..27b803c 100644 (file)
@@ -8,7 +8,7 @@
 <form method='post'>
   {% csrf_token %}
   {{ form.as_p }}
-  <button type='submit'>
+  <button type='submit' class="btn btn-primary">
     {% trans "Add" %}
   </button>
 </form>
index abd1b10..92b832c 100644 (file)
 
 
 {% block accounts-content %}
+  <a href="{% url 'ssh_keys_add' %}" class="btn btn-primary float-end" >
+    {% trans "Add" %}
+  </a>
+
   <h1>{% trans "SSH keys" %}</h1>
 
   {% for key in object_list %}
-    <p>
-      {{ key.comment }} ({{ key.algorithm}} {{ key.bit_length }})<br>
-      {{ key.md5_hash }}
-      <code class="key">{{ key.key }}</code><br>
-      {% trans "Added" %}: {{ key.created_at }}<br>
-      {% trans "Last seen" %}: {{ key.last_seen_at|default:"–" }}<br>
-
-      <a href="{% url 'ssh_keys_delete' key.id %}">
-        {% trans "Delete" %}
-      </a>
-    </p>
-    <hr>
+    <div class="card mb-2">
+      <div class="card-header">
+        {{ key.comment }} ({{ key.algorithm}} {{ key.bit_length }})<br>
+      </div>
+      <div class="card-body">
+        {% trans "Added" %}: {{ key.created_at }}<br>
+        {% trans "Last seen" %}: {{ key.last_seen_at|default:"–" }}<br>
+
+        <br>
+        {{ key.md5_hash }}
+        <code class="key">{{ key.key }}</code><br>
+        <a href="{% url 'ssh_keys_delete' key.id %}" class="btn btn-danger">
+          {% trans "Delete" %}
+        </a>
+      </div>
+    </div>
+  {% empty %}
+    <div class="alert alert-warning">
+      {% translate "You don't have any SSH keys." %}
+    </div>
   {% endfor %}
 
-  <a href="{% url 'ssh_keys_add' %}">
-    {% trans "Add" %}
-  </a>
 
 {% endblock %}