Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
d31f62ebb4 |
|
@ -3,7 +3,7 @@ default_language_version:
|
||||||
exclude: ^.*\b(migrations)\b.*$
|
exclude: ^.*\b(migrations)\b.*$
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v4.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
|
@ -16,21 +16,21 @@ repos:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: 'v0.3.0'
|
rev: 'v0.4.4'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
- --fix
|
- --fix
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.15.1
|
rev: v3.15.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args:
|
args:
|
||||||
- --py311-plus
|
- --py311-plus
|
||||||
exclude: migrations/
|
exclude: migrations/
|
||||||
- repo: https://github.com/adamchainz/django-upgrade
|
- repo: https://github.com/adamchainz/django-upgrade
|
||||||
rev: 1.16.0
|
rev: 1.17.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: django-upgrade
|
- id: django-upgrade
|
||||||
args:
|
args:
|
||||||
|
|
|
@ -12,17 +12,17 @@ authors = [
|
||||||
{ name = "Víðir Valberg Guðmundsson", email = "valberg@orn.li" },
|
{ name = "Víðir Valberg Guðmundsson", email = "valberg@orn.li" },
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Django==5.0.1",
|
"Django==5.0.6",
|
||||||
"django-money==3.4.1",
|
"django-money==3.4.1",
|
||||||
"django-allauth==0.60.0",
|
"django-allauth==0.63.3",
|
||||||
"psycopg[binary]==3.1.16",
|
"psycopg[binary]==3.1.19",
|
||||||
"environs[django]==10.0.0",
|
"environs[django]==11.0.0",
|
||||||
"uvicorn==0.25.0",
|
"uvicorn==0.30.0",
|
||||||
"whitenoise==6.6.0",
|
"whitenoise==6.6.0",
|
||||||
"django-zen-queries==2.1.0",
|
"django-zen-queries==2.1.0",
|
||||||
"django-registries==0.0.3",
|
"django-registries==0.0.3",
|
||||||
"django-view-decorator==0.0.4",
|
"django-view-decorator==0.0.4",
|
||||||
"django-oauth-toolkit==2.3.0",
|
"django-oauth-toolkit==2.4.0",
|
||||||
]
|
]
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ from django.contrib import admin
|
||||||
|
|
||||||
from .models import Membership
|
from .models import Membership
|
||||||
from .models import MembershipType
|
from .models import MembershipType
|
||||||
from .models import ServiceAccess
|
|
||||||
from .models import SubscriptionPeriod
|
from .models import SubscriptionPeriod
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,8 +18,3 @@ class MembershipTypeAdmin(admin.ModelAdmin):
|
||||||
@admin.register(SubscriptionPeriod)
|
@admin.register(SubscriptionPeriod)
|
||||||
class SubscriptionPeriodAdmin(admin.ModelAdmin):
|
class SubscriptionPeriodAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ServiceAccess)
|
|
||||||
class ServiceAccessAdmin(admin.ModelAdmin):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
# Generated by Django 5.0.1 on 2024-01-13 19:20
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django_registries.registry
|
|
||||||
import services.registry
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("membership", "0005_member"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="ServiceAccess",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"service",
|
|
||||||
django_registries.registry.ChoicesField(
|
|
||||||
choices=[],
|
|
||||||
registry=services.registry.ServiceRegistry,
|
|
||||||
verbose_name="service",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "service access",
|
|
||||||
"verbose_name_plural": "service accesses",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddConstraint(
|
|
||||||
model_name="serviceaccess",
|
|
||||||
constraint=models.UniqueConstraint(
|
|
||||||
fields=("user", "service"), name="unique_user_service"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -5,8 +5,6 @@ from django.contrib.postgres.fields import RangeOperators
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from services.registry import ServiceRegistry
|
|
||||||
from utils.mixins import CreatedModifiedAbstract
|
from utils.mixins import CreatedModifiedAbstract
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,22 +111,3 @@ class MembershipType(CreatedModifiedAbstract):
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class ServiceAccess(CreatedModifiedAbstract):
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("service access")
|
|
||||||
verbose_name_plural = _("service accesses")
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=["user", "service"],
|
|
||||||
name="unique_user_service",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
|
||||||
|
|
||||||
service = ServiceRegistry.choices_field(verbose_name=_("service"))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.user} - {self.service}"
|
|
||||||
|
|
|
@ -41,15 +41,12 @@ THIRD_PARTY_APPS = [
|
||||||
"allauth",
|
"allauth",
|
||||||
"allauth.account",
|
"allauth.account",
|
||||||
"django_view_decorator",
|
"django_view_decorator",
|
||||||
"django_registries",
|
|
||||||
"oauth2_provider",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
"utils",
|
"utils",
|
||||||
"accounting",
|
"accounting",
|
||||||
"membership",
|
"membership",
|
||||||
"services",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
@ -157,16 +154,6 @@ ACCOUNT_EMAIL_REQUIRED = True
|
||||||
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
||||||
ACCOUNT_USERNAME_REQUIRED = False
|
ACCOUNT_USERNAME_REQUIRED = False
|
||||||
|
|
||||||
# OAuth2 configuration
|
|
||||||
OAUTH2_PROVIDER = {
|
|
||||||
"OIDC_ENABLED": True,
|
|
||||||
"SCOPES": {
|
|
||||||
"openid": "OpenID Connect scope",
|
|
||||||
"profile": "Profile Information",
|
|
||||||
},
|
|
||||||
"PKCE_REQUIRED": False, # this can be a callable - https://github.com/jazzband/django-oauth-toolkit/issues/711#issuecomment-497073038
|
|
||||||
}
|
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
# We want to log everything to stdout in docker
|
# We want to log everything to stdout in docker
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
|
|
@ -556,4 +556,4 @@ span.time_remaining {
|
||||||
|
|
||||||
.pagination .page-item.disabled .page-link {
|
.pagination .page-item.disabled .page-link {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
|
@ -78,11 +78,13 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "services:list" %}" class="{% active_path "services:list" "current" %}">
|
<a href="/services" class="{% active_path "services" "current" %}">
|
||||||
Services
|
Services
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "account_email" %}" class="{% active_path "account_email" "current" %}">
|
<a href="{% url "account_email" %}" class="{% active_path "account_email" "current" %}">
|
||||||
|
@ -111,7 +113,7 @@
|
||||||
themeSwitcher.addEventListener('click', function() {
|
themeSwitcher.addEventListener('click', function() {
|
||||||
themeSwitcher.classList.toggle('active')
|
themeSwitcher.classList.toggle('active')
|
||||||
let isDark = document.querySelector('html').classList.toggle('dark');
|
let isDark = document.querySelector('html').classList.toggle('dark');
|
||||||
|
|
||||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
62
src/project/templates/services_overview.html
Normal file
62
src/project/templates/services_overview.html
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="content-view">
|
||||||
|
Coming soon!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
<div class="content-view">
|
||||||
|
<h2>Services you subscribe to</h2>
|
||||||
|
<div class="services">
|
||||||
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
<h3>Passit</h3>
|
||||||
|
<p>Passit is a service that blabla</p>
|
||||||
|
<a href="#">Read more …</a>
|
||||||
|
</div>
|
||||||
|
<a>Unsubscribe</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-view">
|
||||||
|
<h2>Available services</h2>
|
||||||
|
<div class="services">
|
||||||
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
<h3>Forgejo</h3>
|
||||||
|
<p>Forgejo is a service that blabla</p>
|
||||||
|
<a href="#">Read more …</a>
|
||||||
|
</div>
|
||||||
|
<a>Subscribe</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
<h3>Mastodon</h3>
|
||||||
|
<p>Mastodon is a service where you can write things to people around the world.</p>
|
||||||
|
<a href="#">Read more …</a>
|
||||||
|
</div>
|
||||||
|
<a>Subscribe</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
<h3>Matrix</h3>
|
||||||
|
<p>Matrix is a service that blabla</p>
|
||||||
|
<a href="#">Read more …</a>
|
||||||
|
</div>
|
||||||
|
<a>Subscribe</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
<h3>NextCloud</h3>
|
||||||
|
<p>NextCloud is a service that blabla</p>
|
||||||
|
<a href="#">Read more …</a>
|
||||||
|
</div>
|
||||||
|
<a>Subscribe</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endcomment %}
|
||||||
|
{% endblock %}
|
|
@ -7,7 +7,6 @@ from django_view_decorator import include_view_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include_view_urls(extra_modules=["project.views"])),
|
path("", include_view_urls(extra_modules=["project.views"])),
|
||||||
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
path("_admin/", admin.site.urls),
|
path("_admin/", admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
# Register your models here.
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ServicesConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "services"
|
|
|
@ -1 +0,0 @@
|
||||||
# Create your models here.
|
|
|
@ -1,40 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django_registries.registry import Interface
|
|
||||||
from django_registries.registry import Registry
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceRegistry(Registry):
|
|
||||||
"""Registry for services"""
|
|
||||||
|
|
||||||
implementations_module = "services"
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceInterface(Interface):
|
|
||||||
"""Interface for services"""
|
|
||||||
|
|
||||||
registry = ServiceRegistry
|
|
||||||
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
url: str
|
|
||||||
|
|
||||||
public: bool = False
|
|
||||||
|
|
||||||
# TODO: add a way to add a something which defines the required fields for a service
|
|
||||||
# - maybe a list of tuples with the field name and the type of the field
|
|
||||||
# this could be used to generate a form for the service, and also to validate
|
|
||||||
# the data saved in a JSONField on the ServiceAccess model
|
|
||||||
|
|
||||||
subscribe_fields: list[tuple[str, forms.Field]] = []
|
|
||||||
|
|
||||||
def get_form(self) -> type:
|
|
||||||
"""Get the form for the service"""
|
|
||||||
print(self.subscribe_fields)
|
|
||||||
return type(
|
|
||||||
"ServiceForm",
|
|
||||||
(forms.Form,),
|
|
||||||
{
|
|
||||||
field_name: field_type
|
|
||||||
for field_name, field_type in self.subscribe_fields
|
|
||||||
},
|
|
||||||
)()
|
|
|
@ -1,51 +0,0 @@
|
||||||
from django import forms
|
|
||||||
|
|
||||||
from .registry import ServiceInterface
|
|
||||||
|
|
||||||
|
|
||||||
class MailService(ServiceInterface):
|
|
||||||
slug = "mail"
|
|
||||||
name = "Mail"
|
|
||||||
url = "https://mail.data.coop"
|
|
||||||
description = "Mail service for data.coop"
|
|
||||||
|
|
||||||
|
|
||||||
class MatrixService(ServiceInterface):
|
|
||||||
slug = "matrix"
|
|
||||||
name = "Matrix"
|
|
||||||
url = "https://matrix.data.coop"
|
|
||||||
description = "Matrix service for data.coop"
|
|
||||||
|
|
||||||
subscribe_fields = [
|
|
||||||
("username", forms.CharField()),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MastodonService(ServiceInterface):
|
|
||||||
slug = "mastodon"
|
|
||||||
name = "Mastodon"
|
|
||||||
url = "https://social.data.coop"
|
|
||||||
description = "Mastodon service for data.coop"
|
|
||||||
|
|
||||||
|
|
||||||
class NextcloudService(ServiceInterface):
|
|
||||||
slug = "nextcloud"
|
|
||||||
name = "Nextcloud"
|
|
||||||
url = "https://cloud.data.coop"
|
|
||||||
description = "Nextcloud service for data.coop"
|
|
||||||
|
|
||||||
|
|
||||||
class HedgeDocService(ServiceInterface):
|
|
||||||
slug = "hedgedoc"
|
|
||||||
name = "HedgeDoc"
|
|
||||||
url = "https://pad.data.coop"
|
|
||||||
public = True
|
|
||||||
description = "HedgeDoc service for data.coop"
|
|
||||||
|
|
||||||
|
|
||||||
class RalllyService(ServiceInterface):
|
|
||||||
slug = "rallly"
|
|
||||||
name = "Rallly"
|
|
||||||
url = "https://when.data.coop"
|
|
||||||
public = True
|
|
||||||
description = "Rallly service for data.coop"
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="content-view">
|
|
||||||
<h2>{{ service.name }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="content-view">
|
|
||||||
<h2>Subscribe to {{ service.name }}</h2>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
{{ form }}
|
|
||||||
|
|
||||||
<button type="submit">
|
|
||||||
Subscribe
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,45 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="content-view">
|
|
||||||
<h2>Services you subscribe to</h2>
|
|
||||||
<div class="services">
|
|
||||||
{% for service in active_services %}
|
|
||||||
<div>
|
|
||||||
<div class="description">
|
|
||||||
<h3>{{ service.name }}</h3>
|
|
||||||
<p>...</p>
|
|
||||||
<a href="#">Read more …</a>
|
|
||||||
</div>
|
|
||||||
<a>Unsubscribe</a>
|
|
||||||
</div>
|
|
||||||
{% empty %}
|
|
||||||
<p>You are not subscribed to any service.</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-view">
|
|
||||||
<h2>Available services</h2>
|
|
||||||
<div class="services">
|
|
||||||
{% for service in non_active_services %}
|
|
||||||
<div>
|
|
||||||
<div class="description">
|
|
||||||
<h3>{{ service.name }}</h3>
|
|
||||||
<p>{{ service.description }}</p>
|
|
||||||
|
|
||||||
<a href="{% url "services:detail" service_slug=service.slug %}">
|
|
||||||
Read more
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href="{{ service.url }}" target="_blank">
|
|
||||||
Visit
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a href="{% url "services:subscribe" service_slug=service.slug %}">Subscribe</a>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1 +0,0 @@
|
||||||
# Create your tests here.
|
|
|
@ -1,86 +0,0 @@
|
||||||
# Create your views here.
|
|
||||||
from django_view_decorator import namespaced_decorator_factory
|
|
||||||
|
|
||||||
from membership.models import ServiceAccess
|
|
||||||
from services.registry import ServiceRegistry
|
|
||||||
from utils.view_utils import render
|
|
||||||
|
|
||||||
|
|
||||||
services_view = namespaced_decorator_factory(
|
|
||||||
namespace="services",
|
|
||||||
base_path="services",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@services_view(
|
|
||||||
paths="",
|
|
||||||
name="list",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def services_overview(request):
|
|
||||||
active_services = [
|
|
||||||
access.service_implementation
|
|
||||||
for access in ServiceAccess.objects.filter(
|
|
||||||
user=request.user,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
active_service_classes = [service.__class__ for service in active_services]
|
|
||||||
|
|
||||||
services = [
|
|
||||||
service
|
|
||||||
for _, service in ServiceRegistry.get_items()
|
|
||||||
if service not in active_service_classes
|
|
||||||
]
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"non_active_services": services,
|
|
||||||
"active_services": active_services,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request=request,
|
|
||||||
template_name="services/services_overview.html",
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@services_view(
|
|
||||||
paths="<str:service_slug>/",
|
|
||||||
name="detail",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def service_detail(request, service_slug):
|
|
||||||
service = ServiceRegistry.get(slug=service_slug)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"service": service,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request=request,
|
|
||||||
template_name="services/service_detail.html",
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@services_view(
|
|
||||||
paths="<str:service_slug>/subscribe/",
|
|
||||||
name="subscribe",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def service_subscribe(request, service_slug):
|
|
||||||
service = ServiceRegistry.get(slug=service_slug)
|
|
||||||
|
|
||||||
# TODO: add a form to subscribe to the service
|
|
||||||
context = {
|
|
||||||
"service": service,
|
|
||||||
"base_path": "services:list",
|
|
||||||
"form": service.get_form(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request=request,
|
|
||||||
template_name="services/service_subscribe.html",
|
|
||||||
context=context,
|
|
||||||
)
|
|
Loading…
Reference in a new issue