More UI!
This commit is contained in:
parent
5a8b6bb3d0
commit
34b24a0dcb
@ -1,4 +1,4 @@
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional
|
||||
|
||||
import urlman
|
||||
from django.db import models
|
||||
@ -126,10 +126,14 @@ class Post(StatorModel):
|
||||
### Local creation ###
|
||||
|
||||
@classmethod
|
||||
def create_local(cls, author: Identity, content: str) -> "Post":
|
||||
def create_local(
|
||||
cls, author: Identity, content: str, summary: Optional[str] = None
|
||||
) -> "Post":
|
||||
post = cls.objects.create(
|
||||
author=author,
|
||||
content=content,
|
||||
summary=summary or None,
|
||||
sensitive=bool(summary),
|
||||
local=True,
|
||||
)
|
||||
post.object_uri = post.author.actor_uri + f"posts/{post.id}/"
|
||||
|
0
activities/templatetags/__init__.py
Normal file
0
activities/templatetags/__init__.py
Normal file
33
activities/templatetags/activity_tags.py
Normal file
33
activities/templatetags/activity_tags.py
Normal file
@ -0,0 +1,33 @@
|
||||
import datetime
|
||||
|
||||
from django import template
|
||||
from django.utils import timezone
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def timedeltashort(value: datetime.datetime):
|
||||
"""
|
||||
A more compact version of timesince
|
||||
"""
|
||||
if not value:
|
||||
return ""
|
||||
# TODO: Handle things in the future properly
|
||||
delta = timezone.now() - value
|
||||
seconds = int(delta.total_seconds())
|
||||
days = delta.days
|
||||
if seconds < 60:
|
||||
text = f"{seconds:0n}s"
|
||||
elif seconds < 60 * 60:
|
||||
minutes = seconds // 60
|
||||
text = f"{minutes:0n}m"
|
||||
elif seconds < 60 * 60 * 24:
|
||||
hours = seconds // (60 * 60)
|
||||
text = f"{hours:0n}h"
|
||||
elif days < 365:
|
||||
text = f"{days:0n}h"
|
||||
else:
|
||||
years = days // 365.25
|
||||
text = f"{years:0n}y"
|
||||
return text
|
@ -1,42 +0,0 @@
|
||||
from django import forms
|
||||
from django.shortcuts import redirect
|
||||
from django.template.defaultfilters import linebreaks_filter
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import FormView
|
||||
|
||||
from activities.models import Post, TimelineEvent
|
||||
from core.forms import FormHelper
|
||||
from users.decorators import identity_required
|
||||
|
||||
|
||||
@method_decorator(identity_required, name="dispatch")
|
||||
class Home(FormView):
|
||||
|
||||
template_name = "activities/home.html"
|
||||
|
||||
class form_class(forms.Form):
|
||||
text = forms.CharField()
|
||||
|
||||
helper = FormHelper(submit_text="Post")
|
||||
|
||||
def get_context_data(self):
|
||||
context = super().get_context_data()
|
||||
context.update(
|
||||
{
|
||||
"timeline_posts": [
|
||||
te.subject_post
|
||||
for te in TimelineEvent.objects.filter(
|
||||
identity=self.request.identity,
|
||||
type=TimelineEvent.Types.post,
|
||||
).order_by("-created")[:100]
|
||||
],
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
Post.create_local(
|
||||
author=self.request.identity,
|
||||
content=linebreaks_filter(form.cleaned_data["text"]),
|
||||
)
|
||||
return redirect(".")
|
70
activities/views/timelines.py
Normal file
70
activities/views/timelines.py
Normal file
@ -0,0 +1,70 @@
|
||||
from django import forms
|
||||
from django.shortcuts import redirect
|
||||
from django.template.defaultfilters import linebreaks_filter
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import FormView, TemplateView
|
||||
|
||||
from activities.models import Post, TimelineEvent
|
||||
from users.decorators import identity_required
|
||||
|
||||
|
||||
@method_decorator(identity_required, name="dispatch")
|
||||
class Home(FormView):
|
||||
|
||||
template_name = "activities/home.html"
|
||||
|
||||
class form_class(forms.Form):
|
||||
text = forms.CharField(
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"placeholder": "What's on your mind?",
|
||||
},
|
||||
)
|
||||
)
|
||||
content_warning = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": "Content Warning",
|
||||
"class": "hidden",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def get_context_data(self):
|
||||
context = super().get_context_data()
|
||||
context["timeline_posts"] = [
|
||||
te.subject_post
|
||||
for te in TimelineEvent.objects.filter(
|
||||
identity=self.request.identity,
|
||||
type=TimelineEvent.Types.post,
|
||||
)
|
||||
.select_related("subject_post", "subject_post__author")
|
||||
.order_by("-created")[:100]
|
||||
]
|
||||
context["current_page"] = "home"
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
Post.create_local(
|
||||
author=self.request.identity,
|
||||
content=linebreaks_filter(form.cleaned_data["text"]),
|
||||
summary=form.cleaned_data.get("content_warning"),
|
||||
)
|
||||
return redirect(".")
|
||||
|
||||
|
||||
@method_decorator(identity_required, name="dispatch")
|
||||
class Federated(TemplateView):
|
||||
|
||||
template_name = "activities/federated.html"
|
||||
|
||||
def get_context_data(self):
|
||||
context = super().get_context_data()
|
||||
context["timeline_posts"] = (
|
||||
Post.objects.filter(visibility=Post.Visibilities.public)
|
||||
.select_related("author")
|
||||
.order_by("-created")[:100]
|
||||
)
|
||||
context["current_page"] = "federated"
|
||||
return context
|
@ -1,11 +0,0 @@
|
||||
from crispy_forms.helper import FormHelper as BaseFormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
|
||||
|
||||
class FormHelper(BaseFormHelper):
|
||||
|
||||
submit_text = "Submit"
|
||||
|
||||
def __init__(self, form=None, submit_text=None):
|
||||
super().__init__(form)
|
||||
self.add_input(Submit("submit", submit_text or "Submit"))
|
@ -1,6 +1,6 @@
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from activities.views.home import Home
|
||||
from activities.views.timelines import Home
|
||||
from users.models import Identity
|
||||
|
||||
|
||||
|
@ -2,7 +2,6 @@ django~=4.1
|
||||
pyld~=2.0.3
|
||||
pillow~=9.3.0
|
||||
urlman~=2.0.1
|
||||
django-crispy-forms~=1.14
|
||||
cryptography~=38.0
|
||||
httpx~=0.23
|
||||
pyOpenSSL~=22.1.0
|
||||
|
@ -22,9 +22,6 @@ ignore_missing_imports = True
|
||||
[mypy-urlman.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-crispy_forms.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-cryptography.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
|
@ -79,6 +79,7 @@ a {
|
||||
--color-bg-main: #26323c;
|
||||
--color-bg-menu: #2e3e4c;
|
||||
--color-bg-box: #1a2631;
|
||||
--color-bg-error: rgb(87, 32, 32);
|
||||
--color-highlight: #449c8c;
|
||||
|
||||
--color-text-duller: #5f6983;
|
||||
@ -90,13 +91,13 @@ a {
|
||||
--color-button-main: #444b5d;
|
||||
--color-button-main-hover: #515d7c;
|
||||
--color-bg3: #444b5d;
|
||||
--color-text-error: rgb(155, 111, 111);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-bg-main);
|
||||
color: var(--color-text-main);
|
||||
font-family: "Raleway", sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
main {
|
||||
@ -123,7 +124,7 @@ header .logo {
|
||||
}
|
||||
|
||||
header .logo:hover {
|
||||
border-bottom: 3px solid rgba(255, 255, 255, 0.2);
|
||||
border-bottom: 3px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
header .logo img {
|
||||
@ -201,6 +202,33 @@ nav a:hover {
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
/* Left-right columns */
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
flex-grow: 1;
|
||||
width: 300px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
width: 250px;
|
||||
background: var(--color-bg-menu);
|
||||
}
|
||||
|
||||
.right-column h2 {
|
||||
background: var(--color-highlight);
|
||||
padding: 8px 10px;
|
||||
font-weight: bold;
|
||||
font-size: 90%;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Icon menus */
|
||||
|
||||
.icon-menu {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -255,43 +283,111 @@ nav a:hover {
|
||||
|
||||
/* Forms */
|
||||
|
||||
form .control-group {
|
||||
margin: 0 0 15px 0;
|
||||
form {
|
||||
padding: 20px 40px 20px 30px;
|
||||
}
|
||||
|
||||
form .asteriskField {
|
||||
display: none;
|
||||
.right-column form {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form h1 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
form p {
|
||||
color: var(--color-text-main);
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
|
||||
form .field {
|
||||
margin: 25px 0 25px 0;
|
||||
background: var(--color-bg-box);
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 10px 10px;
|
||||
}
|
||||
|
||||
.right-column form .field {
|
||||
margin: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form label {
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
font-size: 110%;
|
||||
color: var(--color-text-dull);
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
form label.requiredField::after {
|
||||
content: " (required)";
|
||||
form label small {
|
||||
font-size: 80%;
|
||||
color: var(--color-text-duller);
|
||||
margin-left: 5px;
|
||||
color: var(--color-text-dull);
|
||||
}
|
||||
|
||||
form .help-block {
|
||||
color: var(--color-text-error);
|
||||
padding: 4px 0 0 0;
|
||||
.right-column form label {
|
||||
margin: 5px 10px 5px 10px;
|
||||
}
|
||||
|
||||
form .help {
|
||||
color: var(--color-text-dull);
|
||||
font-size: 90%;
|
||||
margin: 2px 0 6px 0;
|
||||
}
|
||||
|
||||
form .errorlist {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form .errorlist li {
|
||||
color: var(--color-text-main);
|
||||
background: var(--color-bg-error);
|
||||
border-radius: 3px;
|
||||
margin: 5px 0 8px 0;
|
||||
padding: 3px 7px;
|
||||
}
|
||||
|
||||
form .errorlist li::before {
|
||||
content: "\f071";
|
||||
font: var(--fa-font-solid);
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
form .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form input,
|
||||
form select {
|
||||
form select,
|
||||
form textarea {
|
||||
width: 100%;
|
||||
padding: 4px 6px;
|
||||
padding: 5px 7px;
|
||||
background: var(--color-bg-main);
|
||||
border: 1px solid var(--color-input-border);
|
||||
border-radius: 3px;
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
form input:focus {
|
||||
.right-column form.compose input,
|
||||
.right-column form.compose textarea {
|
||||
margin: 0 0 10px 0;
|
||||
border: 0;
|
||||
font-size: 95%;
|
||||
border-radius: 0;
|
||||
background-color: var(--color-bg-box);
|
||||
}
|
||||
|
||||
.right-column form.compose textarea {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
form input:focus,
|
||||
form select:focus,
|
||||
form textarea:focus {
|
||||
outline: none;
|
||||
border: 1px solid var(--color-input-border-active);
|
||||
}
|
||||
@ -311,6 +407,48 @@ form input[type=submit]:hover {
|
||||
background: var(--color-button-main-hover);
|
||||
}
|
||||
|
||||
form .buttons {
|
||||
text-align: right;
|
||||
margin: 25px 0 15px 0;
|
||||
}
|
||||
|
||||
.right-column form .buttons {
|
||||
margin: 5px 10px 5px 0;
|
||||
}
|
||||
|
||||
form button,
|
||||
form .button {
|
||||
padding: 5px 10px;
|
||||
margin: 0 0 0 5px;
|
||||
border-radius: 5px;
|
||||
border: 3px solid rgba(255, 255, 255, 0);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
background-color: var(--color-highlight);
|
||||
color: var(--color-text-main);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
form button.toggle,
|
||||
form .button.toggle {
|
||||
background: var(--color-bg-main);
|
||||
}
|
||||
|
||||
form button.toggle.enabled,
|
||||
form .button.toggle.enabled {
|
||||
background: var(--color-highlight);
|
||||
}
|
||||
|
||||
form button:hover,
|
||||
form .button:hover {
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.right-column form button,
|
||||
.right-column form .button {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* Identities */
|
||||
|
||||
h1.identity {
|
||||
@ -350,29 +488,26 @@ h1.identity small {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.left-column .post {
|
||||
background: var(--color-bg-box);
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.post .icon {
|
||||
height: 48px;
|
||||
width: auto;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.post .author {
|
||||
padding-left: 64px;
|
||||
}
|
||||
|
||||
.post .author a,
|
||||
.post time a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post .author small {
|
||||
font-weight: normal;
|
||||
color: var(--color-text-dull);
|
||||
.post .handle {
|
||||
display: block;
|
||||
padding: 7px 0 10px 64px;
|
||||
}
|
||||
|
||||
.post time {
|
||||
display: block;
|
||||
float: right;
|
||||
padding-left: 64px;
|
||||
color: var(--color-text-duller);
|
||||
}
|
||||
|
1
static/js/hyperscript.min.js
vendored
Executable file
1
static/js/hyperscript.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -12,7 +12,6 @@ INSTALLED_APPS = [
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"crispy_forms",
|
||||
"core",
|
||||
"activities",
|
||||
"users",
|
||||
|
@ -10,7 +10,6 @@ MIDDLEWARE.insert(0, "core.middleware.AlwaysSecureMiddleware")
|
||||
|
||||
# Ensure debug features are on
|
||||
DEBUG = True
|
||||
CRISPY_FAIL_SILENTLY = False
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
|
@ -11,7 +11,6 @@ except KeyError:
|
||||
|
||||
# Ensure debug features are off
|
||||
DEBUG = False
|
||||
CRISPY_FAIL_SILENTLY = True
|
||||
|
||||
# TODO: Allow better setting of allowed_hosts, if we need to
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
@ -1,12 +1,15 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
from activities.views import timelines
|
||||
from core import views as core
|
||||
from stator import views as stator
|
||||
from users.views import activitypub, auth, identity
|
||||
|
||||
urlpatterns = [
|
||||
path("", core.homepage),
|
||||
# Activity views
|
||||
path("federated/", timelines.Federated.as_view()),
|
||||
# Authentication
|
||||
path("auth/login/", auth.Login.as_view()),
|
||||
path("auth/logout/", auth.Logout.as_view()),
|
||||
|
6
templates/activities/_home_menu.html
Normal file
6
templates/activities/_home_menu.html
Normal file
@ -0,0 +1,6 @@
|
||||
<nav>
|
||||
<a href="/" {% if current_page == "home" %}class="selected"{% endif %}>Home</a>
|
||||
<a href="/" {% if current_page == "mentions" %}class="selected"{% endif %}>Mentions</a>
|
||||
<a href="/" {% if current_page == "public" %}class="selected"{% endif %}>Public</a>
|
||||
<a href="/federated/" {% if current_page == "federated" %}class="selected"{% endif %}>Federated</a>
|
||||
</nav>
|
@ -1,4 +1,5 @@
|
||||
{% load static %}
|
||||
{% load activity_tags %}
|
||||
<div class="post">
|
||||
|
||||
{% if post.author.icon_uri %}
|
||||
@ -7,20 +8,20 @@
|
||||
<img src="{% static "img/unknown-icon-128.png" %}" class="icon">
|
||||
{% endif %}
|
||||
|
||||
<h3 class="author">
|
||||
<a href="{{ post.author.urls.view }}">
|
||||
{{ post.author.name_or_handle }} <small>@{{ post.author.handle }}</small>
|
||||
</a>
|
||||
</h3>
|
||||
<time>
|
||||
<a href="{{ post.urls.view }}">
|
||||
{% if post.authored %}
|
||||
{{ post.authored | timesince }} ago
|
||||
{{ post.authored | timedeltashort }}
|
||||
{% else %}
|
||||
{{ post.created | timesince }} ago
|
||||
{{ post.created | timedeltashort }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</time>
|
||||
|
||||
<a href="{{ post.author.urls.view }}" class="handle">
|
||||
{{ post.author.name_or_handle }} <small>@{{ post.author.handle }}</small>
|
||||
</a>
|
||||
|
||||
<div class="content">
|
||||
{{ post.safe_content }}
|
||||
</div>
|
||||
|
21
templates/activities/federated.html
Normal file
21
templates/activities/federated.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Federated Timeline{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include "activities/_home_menu.html" %}
|
||||
|
||||
<section class="columns">
|
||||
<div class="left-column">
|
||||
{% for post in timeline_posts %}
|
||||
{% include "activities/_post.html" %}
|
||||
{% empty %}
|
||||
No posts yet.
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="right-column">
|
||||
<h2>?</h2>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
{% endblock %}
|
@ -1,15 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Home{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include "activities/_home_menu.html" %}
|
||||
|
||||
{% crispy form form.helper %}
|
||||
<section class="columns">
|
||||
|
||||
{% for post in timeline_posts %}
|
||||
{% include "activities/_post.html" %}
|
||||
{% empty %}
|
||||
No posts yet.
|
||||
{% endfor %}
|
||||
<div class="left-column">
|
||||
{% for post in timeline_posts %}
|
||||
{% include "activities/_post.html" %}
|
||||
{% empty %}
|
||||
No posts yet.
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="right-column">
|
||||
<h2>Compose</h2>
|
||||
<form action="." method="POST" class="compose">
|
||||
{% csrf_token %}
|
||||
{{ form.text }}
|
||||
{{ form.content_warning }}
|
||||
<div class="buttons">
|
||||
<span class="button toggle" _="on click toggle .enabled then toggle .hidden on #id_content_warning">CW</span>
|
||||
<button>Post</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
@ -1,11 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="modal identities">
|
||||
<h1>Login</h1>
|
||||
{% crispy form form.helper %}
|
||||
</section>
|
||||
<nav>
|
||||
<a href="." class="selected">Login</a>
|
||||
</nav>
|
||||
<form action="." method="POST">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% include "forms/_field.html" %}
|
||||
{% endfor %}
|
||||
<div class="buttons">
|
||||
<button>Login</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@ -8,6 +8,7 @@
|
||||
<link rel="stylesheet" href="{% static "css/style.css" %}" type="text/css" media="screen" />
|
||||
<link rel="stylesheet" href="{% static "fonts/raleway/raleway.css" %}" type="text/css" />
|
||||
<link rel="stylesheet" href="{% static "fonts/font_awesome/all.min.css" %}" type="text/css" />
|
||||
<script src="{% static "js/hyperscript.min.js" %}"></script>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body class="{% block body_class %}{% endblock %}">
|
||||
|
13
templates/forms/_field.html
Normal file
13
templates/forms/_field.html
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="field">
|
||||
<label for="{{ field.id_for_label }}">
|
||||
{{ field.label }}
|
||||
{% if field.field.required %}<small>(Required)</small>{% endif %}
|
||||
</label>
|
||||
{% if field.help_text %}
|
||||
<p class="help">
|
||||
{{ field.help_text }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{{ field.errors }}
|
||||
{{ field }}
|
||||
</div>
|
@ -1,13 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Create Identity{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include "identity/_identity_menu.html" %}
|
||||
<section class="modal identities">
|
||||
<h1>Create Identity</h1>
|
||||
{% crispy form form.helper %}
|
||||
</section>
|
||||
<form action="." method="POST">
|
||||
<h1>Create New Identity</h1>
|
||||
<p>You can have multiple identities - they are totally separate, and share
|
||||
nothing apart from your login details. Use them for alternates, projects, and more.</p>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% include "forms/_field.html" %}
|
||||
{% endfor %}
|
||||
<div class="buttons">
|
||||
<button>Create</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@ -3,6 +3,10 @@
|
||||
{% block title %}Welcome{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav>
|
||||
<a href="/" class="selected">Home</a>
|
||||
</nav>
|
||||
|
||||
{% for identity in identities %}
|
||||
<a href="{{ identity.urls.view }}">{{ identity }}</a>
|
||||
{% endfor %}
|
||||
|
@ -1,12 +1,7 @@
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
|
||||
from core.forms import FormHelper
|
||||
|
||||
|
||||
class Login(LoginView):
|
||||
class form_class(AuthenticationForm):
|
||||
helper = FormHelper(submit_text="Login")
|
||||
|
||||
template_name = "auth/login.html"
|
||||
|
||||
|
@ -8,7 +8,6 @@ from django.utils.decorators import method_decorator
|
||||
from django.views.generic import FormView, TemplateView, View
|
||||
|
||||
from core.config import Config
|
||||
from core.forms import FormHelper
|
||||
from users.decorators import identity_required
|
||||
from users.models import Domain, Follow, Identity, IdentityStates
|
||||
from users.shortcuts import by_handle_or_404
|
||||
@ -83,28 +82,31 @@ class CreateIdentity(FormView):
|
||||
template_name = "identity/create.html"
|
||||
|
||||
class form_class(forms.Form):
|
||||
username = forms.CharField()
|
||||
name = forms.CharField()
|
||||
|
||||
helper = FormHelper(submit_text="Create")
|
||||
username = forms.CharField(
|
||||
help_text="Must be unique on your domain. Cannot be changed easily. Use only: a-z 0-9 _ -"
|
||||
)
|
||||
domain = forms.ChoiceField(
|
||||
help_text="Pick the domain to make this identity on. Cannot be changed later."
|
||||
)
|
||||
name = forms.CharField(
|
||||
help_text="The display name other users see. You can change this easily."
|
||||
)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["domain"] = forms.ChoiceField(
|
||||
choices=[
|
||||
(domain.domain, domain.domain)
|
||||
for domain in Domain.available_for_user(user)
|
||||
]
|
||||
)
|
||||
self.fields["domain"].choices = [
|
||||
(domain.domain, domain.domain)
|
||||
for domain in Domain.available_for_user(user)
|
||||
]
|
||||
|
||||
def clean_username(self):
|
||||
# Remove any leading @
|
||||
value = self.cleaned_data["username"].lstrip("@")
|
||||
# Remove any leading @ and force it lowercase
|
||||
value = self.cleaned_data["username"].lstrip("@").lower()
|
||||
# Validate it's all ascii characters
|
||||
for character in value:
|
||||
if character not in string.ascii_letters + string.digits + "_-":
|
||||
raise forms.ValidationError(
|
||||
"Only the letters a-z, numbers 0-9, dashes and underscores are allowed."
|
||||
"Only the letters a-z, numbers 0-9, dashes, and underscores are allowed."
|
||||
)
|
||||
return value
|
||||
|
||||
|
Reference in New Issue
Block a user