summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-17 19:21:00 -0700
committerAndrew Godwin2022-11-17 17:33:09 -0700
commit291d7e404e12e1d017403242f8ed199046f0904c (patch)
tree89a60ff7f321537b6a58835aaa726f7e22ed09ba
parent0851fbd1ec09b142608667bf90ee806e59cafb28 (diff)
downloadtakahe-291d7e404e12e1d017403242f8ed199046f0904c.tar.gz
takahe-291d7e404e12e1d017403242f8ed199046f0904c.tar.bz2
takahe-291d7e404e12e1d017403242f8ed199046f0904c.zip
Logged out experience, config, and profiles
-rw-r--r--activities/models/post_attachment.py7
-rw-r--r--activities/views/timelines.py1
-rw-r--r--core/models/config.py124
-rw-r--r--core/views.py2
-rw-r--r--static/css/style.css84
-rw-r--r--static/img/fjords-banner-600.jpgbin0 -> 85557 bytes
-rw-r--r--static/img/fjords-banner-900.jpgbin0 -> 87299 bytes
-rw-r--r--takahe/__init__.py1
-rw-r--r--templates/activities/_menu.html38
-rw-r--r--templates/auth/login.html12
-rw-r--r--templates/base.html45
-rw-r--r--templates/forms/_field.html9
-rw-r--r--templates/identity/_menu.html12
-rw-r--r--templates/identity/view.html2
-rw-r--r--templates/index.html12
-rw-r--r--templates/settings/_menu.html6
-rw-r--r--templates/settings/settings.html2
-rw-r--r--users/views/admin.py22
-rw-r--r--users/views/settings.py30
19 files changed, 295 insertions, 114 deletions
diff --git a/activities/models/post_attachment.py b/activities/models/post_attachment.py
index ee77d29..6ccea08 100644
--- a/activities/models/post_attachment.py
+++ b/activities/models/post_attachment.py
@@ -1,5 +1,8 @@
+from functools import partial
+
from django.db import models
+from core.uploads import upload_namer
from stator.models import State, StateField, StateGraph, StatorModel
@@ -31,7 +34,9 @@ class PostAttachment(StatorModel):
mimetype = models.CharField(max_length=200)
# File may not be populated if it's remote and not cached on our side yet
- file = models.FileField(upload_to="attachments/%Y/%m/%d/", null=True, blank=True)
+ file = models.FileField(
+ upload_to=partial(upload_namer, "attachments"), null=True, blank=True
+ )
remote_url = models.CharField(max_length=500, null=True, blank=True)
diff --git a/activities/views/timelines.py b/activities/views/timelines.py
index 38f9331..65b6c49 100644
--- a/activities/views/timelines.py
+++ b/activities/views/timelines.py
@@ -57,7 +57,6 @@ class Home(FormView):
return redirect(".")
-@method_decorator(identity_required, name="dispatch")
class Local(TemplateView):
template_name = "activities/local.html"
diff --git a/core/models/config.py b/core/models/config.py
index 19ac85d..021bf67 100644
--- a/core/models/config.py
+++ b/core/models/config.py
@@ -1,9 +1,21 @@
+from functools import partial
from typing import ClassVar
import pydantic
+from django.core.files import File
from django.db import models
+from django.templatetags.static import static
from django.utils.functional import classproperty
+from core.uploads import upload_namer
+from takahe import __version__
+
+
+class UploadedImage(str):
+ """
+ Type used to indicate a setting is an image
+ """
+
class Config(models.Model):
"""
@@ -31,7 +43,11 @@ class Config(models.Model):
)
json = models.JSONField(blank=True, null=True)
- image = models.ImageField(blank=True, null=True, upload_to="config/%Y/%m/%d/")
+ image = models.ImageField(
+ blank=True,
+ null=True,
+ upload_to=partial(upload_namer, "config"),
+ )
class Meta:
unique_together = [
@@ -46,60 +62,110 @@ class Config(models.Model):
system: ClassVar["Config.ConfigOptions"] # type: ignore
@classmethod
- def load_system(cls):
+ def load_values(cls, options_class, filters):
"""
- Load all of the system config options and return an object with them
+ Loads config options and returns an object with them
"""
values = {}
- for config in cls.objects.filter(user__isnull=True, identity__isnull=True):
- values[config.key] = config.image or config.json
- return cls.SystemOptions(**values)
+ for config in cls.objects.filter(**filters):
+ values[config.key] = config.image.url if config.image else config.json
+ if values[config.key] is None:
+ del values[config.key]
+ values["version"] = __version__
+ return options_class(**values)
+
+ @classmethod
+ def load_system(cls):
+ """
+ Loads the system config options object
+ """
+ return cls.load_values(
+ cls.SystemOptions,
+ {"identity__isnull": True, "user__isnull": True},
+ )
@classmethod
def load_user(cls, user):
"""
- Load all of the user config options and return an object with them
+ Loads a user config options object
"""
- values = {}
- for config in cls.objects.filter(user=user, identity__isnull=True):
- values[config.key] = config.image or config.json
- return cls.UserOptions(**values)
+ return cls.load_values(
+ cls.SystemOptions,
+ {"identity__isnull": True, "user": user},
+ )
@classmethod
def load_identity(cls, identity):
"""
- Load all of the identity config options and return an object with them
+ Loads a user config options object
"""
- values = {}
- for config in cls.objects.filter(user__isnull=True, identity=identity):
- values[config.key] = config.image or config.json
- return cls.IdentityOptions(**values)
+ return cls.load_values(
+ cls.IdentityOptions,
+ {"identity": identity, "user__isnull": True},
+ )
+
+ @classmethod
+ def set_value(cls, key, value, options_class, filters):
+ config_field = options_class.__fields__[key]
+ if isinstance(value, File):
+ if config_field.type_ is not UploadedImage:
+ raise ValueError(f"Cannot save file to {key} of type: {type(value)}")
+ cls.objects.update_or_create(
+ key=key,
+ defaults={"json": None, "image": value},
+ **filters,
+ )
+ elif value is None:
+ cls.objects.filter(key=key, **filters).delete()
+ else:
+ if not isinstance(value, config_field.type_):
+ raise ValueError(f"Invalid type for {key}: {type(value)}")
+ if value == config_field.default:
+ cls.objects.filter(key=key, **filters).delete()
+ else:
+ cls.objects.update_or_create(
+ key=key,
+ defaults={"json": value},
+ **filters,
+ )
@classmethod
def set_system(cls, key, value):
- config_field = cls.SystemOptions.__fields__[key]
- if not isinstance(value, config_field.type_):
- raise ValueError(f"Invalid type for {key}: {type(value)}")
- cls.objects.update_or_create(
- key=key,
- defaults={"json": value},
+ cls.set_value(
+ key,
+ value,
+ cls.SystemOptions,
+ {"identity__isnull": True, "user__isnull": True},
+ )
+
+ @classmethod
+ def set_user(cls, user, key, value):
+ cls.set_value(
+ key,
+ value,
+ cls.UserOptions,
+ {"identity__isnull": True, "user": user},
)
@classmethod
def set_identity(cls, identity, key, value):
- config_field = cls.IdentityOptions.__fields__[key]
- if not isinstance(value, config_field.type_):
- raise ValueError(f"Invalid type for {key}: {type(value)}")
- cls.objects.update_or_create(
- identity=identity,
- key=key,
- defaults={"json": value},
+ cls.set_value(
+ key,
+ value,
+ cls.IdentityOptions,
+ {"identity": identity, "user__isnull": True},
)
class SystemOptions(pydantic.BaseModel):
+ version: str = __version__
+
site_name: str = "takahē"
highlight_color: str = "#449c8c"
+ site_about: str = "<h2>Welcome!</h2>\n\nThis is a community running Takahē."
+ site_icon: UploadedImage = static("img/icon-128.png")
+ site_banner: UploadedImage = static("img/fjords-banner-600.jpg")
+
post_length: int = 500
identity_max_age: int = 24 * 60 * 60
diff --git a/core/views.py b/core/views.py
index 2ef83cc..fdc6642 100644
--- a/core/views.py
+++ b/core/views.py
@@ -19,7 +19,7 @@ class LoggedOutHomepage(TemplateView):
def get_context_data(self):
return {
- "identities": Identity.objects.filter(local=True),
+ "identities": Identity.objects.filter(local=True).order_by("-created")[:20],
}
diff --git a/static/css/style.css b/static/css/style.css
index 9c9d625..d7b561e 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -104,6 +104,7 @@ body {
color: var(--color-text-main);
font-family: "Raleway", sans-serif;
font-size: 16px;
+ min-height: 100%;
}
main {
@@ -113,6 +114,19 @@ main {
border-radius: 5px;
}
+footer {
+ width: 900px;
+ margin: 0 auto;
+ padding: 0 0 10px 0;
+ color: var(--color-text-duller);
+ text-align: center;
+ font-size: 90%;
+}
+
+footer a {
+ border-bottom: 1px solid var(--color-text-duller);
+}
+
header {
display: flex;
}
@@ -127,6 +141,7 @@ header .logo {
font-size: 130%;
color: var(--color-text-main);
border-bottom: 3px solid rgba(0, 0, 0, 0);
+ z-index: 10;
}
header .logo:hover {
@@ -144,6 +159,7 @@ header menu {
display: flex;
list-style-type: none;
justify-content: flex-start;
+ z-index: 10;
}
header menu a {
@@ -151,7 +167,11 @@ header menu a {
color: #eee;
line-height: 30px;
border-bottom: 3px solid rgba(0, 0, 0, 0);
- border-right: 1px solid var(--color-bg-menu);
+}
+
+body.has-banner header menu a {
+ background: rgba(0, 0, 0, 0.5);
+ border-right: 0;
}
header menu a:hover,
@@ -159,6 +179,12 @@ header menu a.selected {
border-bottom: 3px solid var(--color-highlight);
}
+header menu a i {
+ font-size: 24px;
+ display: inline-block;
+ vertical-align: middle;
+}
+
header menu .gap {
flex-grow: 1;
}
@@ -167,17 +193,11 @@ header menu a.identity {
border-right: 0;
text-align: right;
padding-right: 10px;
- background: var(--color-bg-menu);
+ background: var(--color-bg-menu) !important;
border-radius: 0 5px 0 0;
width: 250px;
}
-header menu a i {
- display: inline-block;
- vertical-align: middle;
- margin-right: 10px;
-}
-
header menu a img {
display: inline-block;
vertical-align: middle;
@@ -267,8 +287,6 @@ nav a i {
/* Icon menus */
-.icon-menu {}
-
.icon-menu>a {
display: block;
margin: 0px 0 20px 0;
@@ -431,6 +449,17 @@ form textarea {
color: var(--color-text-main);
}
+form .clear {
+ color: var(--color-text-main);
+ font-size: 90%;
+ margin: 5px 0 5px 0;
+}
+
+form .clear input {
+ display: inline;
+ width: 32px;
+}
+
.right-column form.compose input,
.right-column form.compose textarea {
margin: 0 0 10px 0;
@@ -531,6 +560,16 @@ form .button:hover {
padding: 2px 6px;
}
+/* Logged out homepage */
+
+.about img.banner {
+ width: calc(100% + 30px);
+ height: auto;
+ object-fit: cover;
+ margin: -65px -15px 0 -15px;
+ display: block;
+}
+
/* Identities */
h1.identity {
@@ -542,7 +581,8 @@ h1.identity .banner {
height: 200px;
object-fit: cover;
display: block;
- margin: 0 0 20px 0;
+ width: calc(100% + 30px);
+ margin: -65px -15px 20px -15px;
}
h1.identity .icon {
@@ -723,26 +763,16 @@ h1.identity small {
border-radius: 0;
}
- header .logo {
- border-radius: 0;
+ footer {
+ width: 100%;
+ background-color: var(--color-bg-box);
+ padding: 10px 0;
}
-}
-
-
-
-@media (max-width: 800px) {
- header menu a {
- font-size: 0;
- padding: 10px 20px 4px 20px;
+ header .logo {
+ border-radius: 0;
}
- header menu a i {
- display: inline-block;
- vertical-align: middle;
- margin: 0;
- font-size: 20px;
- }
}
diff --git a/static/img/fjords-banner-600.jpg b/static/img/fjords-banner-600.jpg
new file mode 100644
index 0000000..4d4fed8
--- /dev/null
+++ b/static/img/fjords-banner-600.jpg
Binary files differ
diff --git a/static/img/fjords-banner-900.jpg b/static/img/fjords-banner-900.jpg
new file mode 100644
index 0000000..2c46c17
--- /dev/null
+++ b/static/img/fjords-banner-900.jpg
Binary files differ
diff --git a/takahe/__init__.py b/takahe/__init__.py
index e69de29..493f741 100644
--- a/takahe/__init__.py
+++ b/takahe/__init__.py
@@ -0,0 +1 @@
+__version__ = "0.3.0"
diff --git a/templates/activities/_menu.html b/templates/activities/_menu.html
index 6bb18c2..a671712 100644
--- a/templates/activities/_menu.html
+++ b/templates/activities/_menu.html
@@ -2,15 +2,35 @@
<a href="/" {% if current_page == "home" %}class="selected"{% endif %}>
<i class="fa-solid fa-home"></i> Home
</a>
- <a href="/notifications/" {% if current_page == "notifications" %}class="selected"{% endif %}>
- <i class="fa-solid fa-at"></i> Notifications
- </a>
- <a href="/local/" {% if current_page == "local" %}class="selected"{% endif %}>
- <i class="fa-solid fa-city"></i> Local
- </a>
- <a href="/federated/" {% if current_page == "federated" %}class="selected"{% endif %}>
- <i class="fa-solid fa-globe"></i> Federated
- </a>
+ {% if request.user.is_authenticated %}
+ <a href="{% url "notifications" %}" {% if current_page == "notifications" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-at"></i> Notifications
+ </a>
+ <a href="{% url "local" %}" {% if current_page == "local" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-city"></i> Local
+ </a>
+ <a href="{% url "federated" %}" {% if current_page == "federated" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-globe"></i> Federated
+ </a>
+ <h3></h3>
+ <a href="{% url "compose" %}" {% if top_section == "compose" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-feather"></i> Compose
+ </a>
+ <a href="{% url "search" %}" {% if top_section == "search" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-search"></i> Search
+ </a>
+ <a href="{% url "settings" %}" {% if top_section == "settings" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-gear"></i> Settings
+ </a>
+ {% else %}
+ <a href="/local/" {% if current_page == "local" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-city"></i> Local Posts
+ </a>
+ <h3></h3>
+ <a href="/auth/signup/" {% if current_page == "signup" %}class="selected"{% endif %}>
+ <i class="fa-solid fa-user-plus"></i> Create Account
+ </a>
+ {% endif %}
</nav>
{% if current_page == "home" %}
diff --git a/templates/auth/login.html b/templates/auth/login.html
index c892c78..b3b0a05 100644
--- a/templates/auth/login.html
+++ b/templates/auth/login.html
@@ -3,14 +3,14 @@
{% block title %}Login{% endblock %}
{% block content %}
- <nav>
- <a href="." class="selected">Login</a>
- </nav>
<form action="." method="POST">
{% csrf_token %}
- {% for field in form %}
- {% include "forms/_field.html" %}
- {% endfor %}
+ <fieldset>
+ <legend>Login</legend>
+ {% for field in form %}
+ {% include "forms/_field.html" %}
+ {% endfor %}
+ </fieldset>
<div class="buttons">
<button>Login</button>
</div>
diff --git a/templates/base.html b/templates/base.html
index edcb11a..b64f4f5 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -23,56 +23,57 @@
<main>
<header>
<a class="logo" href="/">
- <img src="{% static "img/icon-128.png" %}" width="32">
+ <img src="{{ config.site_icon }}" width="32">
{{ config.site_name }}
</a>
<menu>
{% if user.is_authenticated %}
<a href="{% url "compose" %}" title="Compose" {% if top_section == "compose" %}class="selected"{% endif %}>
- <i class="fa-solid fa-feather"></i> Compose
+ <i class="fa-solid fa-feather"></i>
</a>
<a href="{% url "search" %}" title="Search" {% if top_section == "search" %}class="selected"{% endif %}>
- <i class="fa-solid fa-search"></i> Search
+ <i class="fa-solid fa-search"></i>
</a>
<a href="{% url "settings" %}" title="Settings" {% if top_section == "settings" %}class="selected"{% endif %}>
- <i class="fa-solid fa-gear"></i> Settings
+ <i class="fa-solid fa-gear"></i>
</a>
<div class="gap"></div>
<a href="/identity/select/" class="identity">
{% if not request.identity %}
No Identity
<img src="{% static "img/unknown-icon-128.png" %}" title="No identity selected">
- {% elif request.identity.icon %}
- {{ request.identity.username }}
- <img src="{{ request.identity.icon.url }}" title="{{ request.identity.handle }}">
- {% elif request.identity.icon_uri %}
- {{ request.identity.username }}
- <img src="{{ request.identity.icon_uri }}" title="{{ request.identity.handle }}">
{% else %}
{{ request.identity.username }}
- <img src="{% static "img/unknown-icon-128.png" %}" title="{{ request.identity.handle }}">
+ <img src="{{ request.identity.local_icon_url }}" title="{{ request.identity.handle }}">
{% endif %}
</a>
{% else %}
- <a href="/auth/login/"><i class="fa-solid fa-right-to-bracket"></i> Login</a>
+ <div class="gap"></div>
+ <a href="/auth/login/" class="identity"><i class="fa-solid fa-right-to-bracket"></i> Login</a>
{% endif %}
</menu>
</header>
{% block full_content %}
- <div class="columns">
- <div class="left-column">
- {% block content %}
- {% endblock %}
+ {% block pre_content %}
+ {% endblock %}
+ <div class="columns">
+ <div class="left-column">
+ {% block content %}
+ {% endblock %}
+ </div>
+ <div class="right-column">
+ {% block right_content %}
+ {% include "activities/_menu.html" %}
+ {% endblock %}
+ </div>
</div>
- <div class="right-column">
- {% block right_content %}
- {% include "activities/_menu.html" %}
- {% endblock %}
- </div>
- </div>
{% endblock %}
</main>
+ <footer>
+ <span>Powered by <a href="https://jointakahe.com">Takahē {{ config.version }}</a></span>
+ </footer>
+
</body>
</html>
diff --git a/templates/forms/_field.html b/templates/forms/_field.html
index 595546d..99db819 100644
--- a/templates/forms/_field.html
+++ b/templates/forms/_field.html
@@ -10,9 +10,14 @@
</p>
{% endif %}
{{ field.errors }}
+ {% if field.field.widget.input_type == "file" and field.value %}
+ <div class="clear">
+ <input type="checkbox" class="clear" name="{{ field.name }}__clear"> Clear current value</input>
+ </div>
+ {% endif %}
{{ field }}
</div>
- {% if preview %}
- <img class="preview" src="{{ preview }}">
+ {% if field.field.widget.input_type == "file" %}
+ <img class="preview" src="{{ field.value }}">
{% endif %}
</div>
diff --git a/templates/identity/_menu.html b/templates/identity/_menu.html
index fff70cb..f841284 100644
--- a/templates/identity/_menu.html
+++ b/templates/identity/_menu.html
@@ -1,5 +1,11 @@
<nav>
- <a href="/identity/select/" {% if identities %}class="selected"{% endif %}>Select Identity</a>
- <a href="/identity/create/" {% if form %}class="selected"{% endif %}>Create Identity</a>
- <a href="/auth/logout/">Logout</a>
+ <a href="/identity/select/" {% if identities %}class="selected"{% endif %}>
+ <i class="fa-solid fa-user"></i> Select Identity
+ </a>
+ <a href="/identity/create/" {% if form %}class="selected"{% endif %}>
+ <i class="fa-solid fa-plus"></i> Create Identity
+ </a>
+ <a href="/auth/logout/">
+ <i class="fa-solid fa-right-from-bracket"></i> Logout
+ </a>
</nav>
diff --git a/templates/identity/view.html b/templates/identity/view.html
index 0dd0592..223c2bb 100644
--- a/templates/identity/view.html
+++ b/templates/identity/view.html
@@ -2,6 +2,8 @@
{% block title %}{{ identity }}{% endblock %}
+{% block body_class %}has-banner{% endblock %}
+
{% block content %}
<h1 class="identity">
{% if identity.local_image_url %}
diff --git a/templates/index.html b/templates/index.html
index 9e09a43..79f81cf 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -2,12 +2,14 @@
{% block title %}Welcome{% endblock %}
-{% block content %}
- <nav>
- <a href="/" class="selected">Home</a>
- </nav>
+{% block content %}
+ <div class="about">
+ <img class="banner" src="{{ config.site_banner }}">
+ {{ config.site_about|safe|linebreaks }}
+ </div>
+ <h2>People</h2>
{% for identity in identities %}
- <a href="{{ identity.urls.view }}">{{ identity }}</a>
+ {% include "activities/_identity.html" %}
{% endfor %}
{% endblock %}
diff --git a/templates/settings/_menu.html b/templates/settings/_menu.html
index e2dc70b..d85c878 100644
--- a/templates/settings/_menu.html
+++ b/templates/settings/_menu.html
@@ -11,6 +11,9 @@
<a href="#" {% if section == "login" %}class="selected"{% endif %}>
<i class="fa-solid fa-key"></i> Login &amp; Security
</a>
+ <a href="/auth/logout/">
+ <i class="fa-solid fa-right-from-bracket"></i> Logout
+ </a>
<h3>Administration</h3>
<a href="{% url "admin_basic" %}" {% if section == "basic" %}class="selected"{% endif %}>
<i class="fa-solid fa-book"></i> Basic
@@ -24,5 +27,8 @@
<a href="{% url "admin_identities" %}" {% if section == "identities" %}class="selected"{% endif %}>
<i class="fa-solid fa-id-card"></i> Identities
</a>
+ <a href="/djadmin">
+ <i class="fa-solid fa-gear"></i> Django Admin
+ </a>
{% endif %}
</nav>
diff --git a/templates/settings/settings.html b/templates/settings/settings.html
index a933627..36a6c10 100644
--- a/templates/settings/settings.html
+++ b/templates/settings/settings.html
@@ -3,7 +3,7 @@
{% block subtitle %}{{ section.title }}{% endblock %}
{% block content %}
- <form action="." method="POST">
+ <form action="." method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% for title, fields in fieldsets.items %}
<fieldset>
diff --git a/users/views/admin.py b/users/views/admin.py
index 9476417..d7f23e8 100644
--- a/users/views/admin.py
+++ b/users/views/admin.py
@@ -40,7 +40,6 @@ class BasicPage(AdminSettingsPage):
options = {
"site_name": {
"title": "Site Name",
- "help_text": "Shown in the top-left of the page, and titles",
},
"highlight_color": {
"title": "Highlight Color",
@@ -50,10 +49,29 @@ class BasicPage(AdminSettingsPage):
"title": "Maximum Post Length",
"help_text": "The maximum number of characters allowed per post",
},
+ "site_about": {
+ "title": "About This Site",
+ "help_text": "Displayed on the homepage and the about page",
+ "display": "textarea",
+ },
+ "site_icon": {
+ "title": "Site Icon",
+ "help_text": "Minimum size 64x64px. Should be square.",
+ },
+ "site_banner": {
+ "title": "Site Banner",
+ "help_text": "Must be at least 650px wide. 3:1 ratio of width:height recommended.",
+ },
}
layout = {
- "Branding": ["site_name", "highlight_color"],
+ "Branding": [
+ "site_name",
+ "site_about",
+ "site_icon",
+ "site_banner",
+ "highlight_color",
+ ],
"Posts": ["post_length"],
}
diff --git a/users/views/settings.py b/users/views/settings.py
index 88e4cd3..d823676 100644
--- a/users/views/settings.py
+++ b/users/views/settings.py
@@ -2,18 +2,19 @@ from functools import partial
from typing import ClassVar, Dict, List
from django import forms
+from django.core.files import File
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.generic import FormView, RedirectView
from PIL import Image, ImageOps
-from core.models import Config
+from core.models.config import Config, UploadedImage
from users.decorators import identity_required
@method_decorator(identity_required, name="dispatch")
class SettingsRoot(RedirectView):
- url = "/settings/interface/"
+ pattern_name = "settings_profile"
@method_decorator(identity_required, name="dispatch")
@@ -41,8 +42,16 @@ class SettingsPage(FormView):
choices=[(True, "Enabled"), (False, "Disabled")]
),
)
+ elif config_field.type_ is UploadedImage:
+ form_field = forms.ImageField
elif config_field.type_ is str:
- form_field = forms.CharField
+ if details.get("display") == "textarea":
+ form_field = partial(
+ forms.CharField,
+ widget=forms.Textarea,
+ )
+ else:
+ form_field = forms.CharField
elif config_field.type_ is int:
form_field = forms.IntegerField
else:
@@ -80,6 +89,15 @@ class SettingsPage(FormView):
def form_valid(self, form):
# Save each key
for field in form:
+ if field.field.__class__.__name__ == "ImageField":
+ # These can be cleared with an extra checkbox
+ if self.request.POST.get(f"{field.name}__clear"):
+ self.save_config(field.name, None)
+ continue
+ # We shove the preview values in initial_data, so only save file
+ # fields if they have a File object.
+ if not isinstance(form.cleaned_data[field.name], File):
+ continue
self.save_config(
field.name,
form.cleaned_data[field.name],
@@ -128,6 +146,8 @@ class ProfilePage(FormView):
return {
"name": self.request.identity.name,
"summary": self.request.identity.summary,
+ "icon": self.request.identity.icon.url,
+ "image": self.request.identity.image.url,
}
def get_context_data(self):
@@ -142,12 +162,12 @@ class ProfilePage(FormView):
# Resize images
icon = form.cleaned_data.get("icon")
image = form.cleaned_data.get("image")
- if icon:
+ if isinstance(icon, File):
resized_image = ImageOps.fit(Image.open(icon), (400, 400))
icon.open()
resized_image.save(icon)
self.request.identity.icon = icon
- if image:
+ if isinstance(image, File):
resized_image = ImageOps.fit(Image.open(image), (1500, 500))
image.open()
resized_image.save(image)