A few more fixes from going live
This commit is contained in:
parent
774e91c8a2
commit
6e79527bb5
22
README.md
22
README.md
|
@ -1,30 +1,14 @@
|
||||||
![takahē](static/img/logo-128.png)
|
![takahē](static/img/logo-128.png)
|
||||||
|
|
||||||
A *very experimental* Fediverse server for microblogging/"toots". Not fully functional yet -
|
An *experimental* Fediverse server for microblogging/"toots". Not fully functional yet -
|
||||||
I'm still working on making all the basic bits work! For more background and information,
|
I'm still working on making all the basic bits work! For more background and information,
|
||||||
see [my blog posts about it](https://aeracode.org/category/takahe/).
|
see [jointakahe.org]](https://jointakahe.org/).
|
||||||
|
|
||||||
Indended features:
|
|
||||||
|
|
||||||
* Can run on serverless hosting (no need for worker daemons)
|
|
||||||
* Multiple account domains possible per server
|
|
||||||
* Async evented core for fan-out/delivery
|
|
||||||
* Mastodon client API compatible (eventually)
|
|
||||||
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Requirements:
|
See [the documentation](https://takahe-server.readthedocs.io)
|
||||||
|
|
||||||
- **Python** 3.11
|
|
||||||
- **PostgreSQL** 14+
|
|
||||||
- **Lots of patience** This is *very experimental*
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
More deployment docs will come soon! Just know that you need to run the Takahē
|
|
||||||
Django app, and then either hit `/.stator/runner/` or run `./manage.py runstator`
|
|
||||||
at least every 30 seconds.
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
|
|
@ -617,6 +617,7 @@ form .button:hover {
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
margin: -65px -15px 0 -15px;
|
margin: -65px -15px 0 -15px;
|
||||||
|
border-radius: 5px 0 0 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,6 +634,7 @@ h1.identity .banner {
|
||||||
display: block;
|
display: block;
|
||||||
width: calc(100% + 30px);
|
width: calc(100% + 30px);
|
||||||
margin: -65px -15px 20px -15px;
|
margin: -65px -15px 20px -15px;
|
||||||
|
border-radius: 5px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1.identity .icon {
|
h1.identity .icon {
|
||||||
|
@ -644,10 +646,10 @@ h1.identity .icon {
|
||||||
|
|
||||||
h1.identity small {
|
h1.identity small {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 80%;
|
font-size: 60%;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: var(--color-text-dull);
|
color: var(--color-text-dull);
|
||||||
margin: -10px 0 0 0;
|
margin: -5px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bio {
|
.bio {
|
||||||
|
@ -834,6 +836,11 @@ h1.identity small {
|
||||||
header menu a.identity {
|
header menu a.identity {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
padding: 10px 10px 0 0;
|
padding: 10px 10px 0 0;
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header menu a.identity i {
|
||||||
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-column {
|
.right-column {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,66 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="1024"
|
||||||
|
height="1024"
|
||||||
|
viewBox="0 0 270.93333 270.93333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||||
|
sodipodi:docname="icon-admin.svg"
|
||||||
|
inkscape:export-filename="icon-admin-512.png"
|
||||||
|
inkscape:export-xdpi="12.000001"
|
||||||
|
inkscape:export-ydpi="12.000001"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.5946522"
|
||||||
|
inkscape:cx="761.78983"
|
||||||
|
inkscape:cy="461.61437"
|
||||||
|
inkscape:current-layer="layer1"><inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid111" /></sodipodi:namedview><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient717"><stop
|
||||||
|
style="stop-color:#dc0000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop713" /><stop
|
||||||
|
style="stop-color:#b50000;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop715" /></linearGradient><linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient717"
|
||||||
|
id="linearGradient719"
|
||||||
|
x1="228.25317"
|
||||||
|
y1="14.682952"
|
||||||
|
x2="140.6004"
|
||||||
|
y2="261.17859"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"><rect
|
||||||
|
style="fill:url(#linearGradient719);fill-opacity:1;stroke-width:0.264583"
|
||||||
|
id="rect115"
|
||||||
|
width="270.93332"
|
||||||
|
height="270.93332"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
rx="33.866665" /><path
|
||||||
|
id="path18329"
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
d="M 81.445158 44.391606 C 71.497762 44.34326 59.998982 45.079969 48.053398 47.437415 C 15.973572 53.768324 -5.0132687e-15 69.410234 0 69.410234 L 0 270.93333 L 126.36231 270.93333 C 126.36231 270.93333 127.25234 270.93314 122.02201 253.61449 C 116.79165 236.29585 123.37369 206.01207 150.06991 203.11525 C 152.12765 202.89196 153.29599 201.80052 153.81697 200.1361 C 163.3758 199.05018 175.31632 198.19731 189.82666 198.01634 C 221.49006 197.62143 257.75843 201.03217 257.75843 201.03217 C 257.75843 201.03217 245.16914 168.19766 229.14105 143.28169 C 212.31497 117.12523 195.16587 101.64392 195.16587 101.64392 C 179.27533 82.720553 145.61897 53.85519 113.77238 47.437415 C 113.77238 47.437415 100.43564 44.483902 81.445158 44.391606 z M 94.104333 114.79403 A 17.130112 17.130112 0 0 1 111.23455 131.92425 A 17.130112 17.130112 0 0 1 94.104333 149.05447 A 17.130112 17.130112 0 0 1 76.974113 131.92425 A 17.130112 17.130112 0 0 1 94.104333 114.79403 z " /></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -162,6 +162,8 @@ class Identity(StatorModel):
|
||||||
actor_uri, handle = async_to_sync(cls.fetch_webfinger)(
|
actor_uri, handle = async_to_sync(cls.fetch_webfinger)(
|
||||||
f"{username}@{domain}"
|
f"{username}@{domain}"
|
||||||
)
|
)
|
||||||
|
if handle is None:
|
||||||
|
return None
|
||||||
username, domain = handle.split("@")
|
username, domain = handle.split("@")
|
||||||
domain = Domain.get_remote_domain(domain)
|
domain = Domain.get_remote_domain(domain)
|
||||||
return cls.objects.create(
|
return cls.objects.create(
|
||||||
|
|
|
@ -6,10 +6,10 @@ from django.core.files import File
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import FormView, RedirectView
|
from django.views.generic import FormView, RedirectView
|
||||||
from PIL import Image, ImageOps
|
|
||||||
|
|
||||||
from core.models.config import Config, UploadedImage
|
from core.models.config import Config, UploadedImage
|
||||||
from users.decorators import identity_required
|
from users.decorators import identity_required
|
||||||
|
from users.views.settings.profile import ProfilePage # noqa
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
@ -119,59 +119,6 @@ class InterfacePage(SettingsPage):
|
||||||
layout = {"Posting": ["toot_mode"]}
|
layout = {"Posting": ["toot_mode"]}
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
|
||||||
class ProfilePage(FormView):
|
|
||||||
"""
|
|
||||||
Lets the identity's profile be edited
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_name = "settings/profile.html"
|
|
||||||
extra_context = {"section": "profile"}
|
|
||||||
|
|
||||||
class form_class(forms.Form):
|
|
||||||
name = forms.CharField(max_length=500)
|
|
||||||
summary = forms.CharField(
|
|
||||||
widget=forms.Textarea,
|
|
||||||
required=False,
|
|
||||||
help_text="Describe you and your interests",
|
|
||||||
label="Bio",
|
|
||||||
)
|
|
||||||
icon = forms.ImageField(
|
|
||||||
required=False, help_text="Shown next to all your posts and activities"
|
|
||||||
)
|
|
||||||
image = forms.ImageField(
|
|
||||||
required=False, help_text="Shown at the top of your profile"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
return {
|
|
||||||
"name": self.request.identity.name,
|
|
||||||
"summary": self.request.identity.summary,
|
|
||||||
"icon": self.request.identity.icon and self.request.identity.icon.url,
|
|
||||||
"image": self.request.identity.image and self.request.identity.image.url,
|
|
||||||
}
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
# Update identity name and summary
|
|
||||||
self.request.identity.name = form.cleaned_data["name"]
|
|
||||||
self.request.identity.summary = form.cleaned_data["summary"]
|
|
||||||
# Resize images
|
|
||||||
icon = form.cleaned_data.get("icon")
|
|
||||||
image = form.cleaned_data.get("image")
|
|
||||||
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 isinstance(image, File):
|
|
||||||
resized_image = ImageOps.fit(Image.open(image), (1500, 500))
|
|
||||||
image.open()
|
|
||||||
resized_image.save(image)
|
|
||||||
self.request.identity.image = image
|
|
||||||
self.request.identity.save()
|
|
||||||
return redirect(".")
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
class SecurityPage(FormView):
|
class SecurityPage(FormView):
|
||||||
"""
|
"""
|
|
@ -0,0 +1,63 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
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
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
from users.decorators import identity_required
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
class ProfilePage(FormView):
|
||||||
|
"""
|
||||||
|
Lets the identity's profile be edited
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name = "settings/profile.html"
|
||||||
|
extra_context = {"section": "profile"}
|
||||||
|
|
||||||
|
class form_class(forms.Form):
|
||||||
|
name = forms.CharField(max_length=500)
|
||||||
|
summary = forms.CharField(
|
||||||
|
widget=forms.Textarea,
|
||||||
|
required=False,
|
||||||
|
help_text="Describe you and your interests",
|
||||||
|
label="Bio",
|
||||||
|
)
|
||||||
|
icon = forms.ImageField(
|
||||||
|
required=False, help_text="Shown next to all your posts and activities"
|
||||||
|
)
|
||||||
|
image = forms.ImageField(
|
||||||
|
required=False, help_text="Shown at the top of your profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
return {
|
||||||
|
"name": self.request.identity.name,
|
||||||
|
"summary": self.request.identity.summary,
|
||||||
|
"icon": self.request.identity.icon and self.request.identity.icon.url,
|
||||||
|
"image": self.request.identity.image and self.request.identity.image.url,
|
||||||
|
}
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
# Update identity name and summary
|
||||||
|
self.request.identity.name = form.cleaned_data["name"]
|
||||||
|
self.request.identity.summary = form.cleaned_data["summary"]
|
||||||
|
# Resize images
|
||||||
|
icon = form.cleaned_data.get("icon")
|
||||||
|
image = form.cleaned_data.get("image")
|
||||||
|
if isinstance(icon, File):
|
||||||
|
resized_image = ImageOps.fit(Image.open(icon), (400, 400))
|
||||||
|
new_icon_bytes = io.BytesIO()
|
||||||
|
resized_image.save(new_icon_bytes, format=icon.format)
|
||||||
|
self.request.identity.icon.save(icon.name, File(new_icon_bytes))
|
||||||
|
if isinstance(image, File):
|
||||||
|
resized_image = ImageOps.fit(Image.open(image), (400, 400))
|
||||||
|
new_image_bytes = io.BytesIO()
|
||||||
|
resized_image.save(new_image_bytes, format=image.format)
|
||||||
|
self.request.identity.image.save(image.name, File(new_image_bytes))
|
||||||
|
self.request.identity.save()
|
||||||
|
return redirect(".")
|
Loading…
Reference in New Issue