Add theme support and move preferences into the database

Squashed commit of the following:

commit be4e1ab286f54caa6f44367d2de3cf9e6cb69e68
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Aug 28 12:03:17 2018 -0400

    Set timezone in session as early as possible

commit f1c03349c40ca1aca7f77c32333ee83585495626
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Aug 28 10:06:18 2018 -0400

    Don't hide all .is-hidden in brutalist themes

commit f35a6c5600b091fe0ec4ad78eb5f1a02a4945a65
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Aug 28 09:44:10 2018 -0400

    Rename vt240don.css to vt240don-amber.css

commit 074478937af309d10ff2d7f8f482dbff20ac7cf9
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Aug 28 09:41:43 2018 -0400

    Tweak vt240 amber theme and add vt240 green theme

commit b205cfe7376d067799863d7db1ce3c4530b0ba74
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Aug 28 08:39:11 2018 -0400

    Remove caching of Mastodon connections

    There's not a good way of invalidating them, and they've started causing
    timeouts in this branch.

commit c24a697a39173ff23391220fabad0dc6605cb5ef
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 14:46:21 2018 -0400

    Make minimal-large a little less bare

commit 6fee850cf6ba1edb573a7fedf43af3a343410c9b
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 14:24:57 2018 -0400

    Add minimal-large theme

commit b345dedbfcf55c3142d07a6f5320c677cfb534f0
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 13:19:51 2018 -0400

    Add vt240don style by @enkiv2

commit f013ff235663ee21f2d0962a019ce7bc3c80f7b0
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 09:15:51 2018 -0400

    Fix crash in search

commit 76c4134e9ddebbdf8a8dccab79361e71438877e6
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:35:02 2018 -0400

    Fix crash in oauth_callback

commit d725018d6a34544ac8c92deac27502c457d4d3c8
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:31:19 2018 -0400

    Fix crash in oauth_callback

commit 830264359dbdbf7c34e1d1ae715cb68016cd47f4
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:28:17 2018 -0400

    Fix crash in oauth_callback

commit 2a525298995d108a290484786a7493bfb99af2c8
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:27:03 2018 -0400

    Fix crash in oauth_callback

commit d43eb63146eb21a283290287b9db4d518f36435e
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:25:27 2018 -0400

    Fix crash in oauth_callback

commit ae9a58a0ae010fdc12a7498d691b7b8f8da95e03
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:12:19 2018 -0400

    Fix up default values for default themes again

commit a8fde1710b4d23f2bdd0ced4eeffd44b4e80b756
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:11:18 2018 -0400

    Fixes for dark theme

commit c4a7501cf263afbdc6bf65623e9c8dc5f03f40f7
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Aug 27 08:11:04 2018 -0400

    Small fix in full toot view

commit 69caeea0010e7d59106fba780744592ed9145f61
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 22:50:53 2018 -0400

    Fix up settings display, navbar, typo in links

commit 0e24a394eabe78d8cba864beaedb03a42f916a20
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 22:36:58 2018 -0400

    Fix default theme paths

commit aca5c53561cd8647d42244fd51720e146fddbf94
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 22:35:33 2018 -0400

    More themes prep

commit 6921c55a41db14e155b5ed62c48e3e67d8626b13
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 21:42:53 2018 -0400

    Actually load themes

commit 134952ff17e3a8672e21175395282d91f48f1d2b
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 21:34:06 2018 -0400

    Base and fullbrutalism themes work, but only coincidentally

commit 658aca52955507eda007165e9c44031d417e0076
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 19:47:48 2018 -0400

    Make filtering preference work again

commit 90267055b5a2fc51337b0a3e34b3a5643088e17f
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 19:34:47 2018 -0400

    Fix up settings view, form, and model

commit d58769ecdffad239cc8aa37ebca9c2e87372ebd0
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 19:12:06 2018 -0400

    Fix up old_login to work with new preferences system

commit d8b084a379894a3a40c3eed1c87465f7c77ad7eb
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sun Aug 26 18:11:03 2018 -0400

    Update settings view to use preferences system

commit 49471ae97131bdb0d8152411afdf1987d6c64652
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sat Aug 25 10:53:04 2018 -0400

    Update login logic to match new account models, simplify.

    Not tested yet.

commit 57cce7c0f45fe07dd522a61c9e056db982455843
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Sat Aug 25 10:18:53 2018 -0400

    Fix up models and forms to meet current needs

commit 0634c038ee9220164662d1e933a2f1c83e5af70d
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Fri Aug 24 22:42:07 2018 -0400

    Update fields in models for settings branch

commit 510509e28254cb3ee38b70538d1c0b788448d624
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Fri Jun 22 08:39:42 2018 -0400

    Update settings view a bit; still in progress

commit 5a91a57ceb1d737ed371bd6800ddf6ea0a917a8f
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Jun 19 06:27:19 2018 -0400

    Add forms object for new preferences

commit af4883c172d2b66d83c55b00cee1490fa88ddd4f
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Tue Jun 19 06:26:35 2018 -0400

    Make sure newly created accounts have preferences attached

commit f0ae97b2bf8735bd5c369f273fb51d74deb05450
Author: Jason McBrayer <jmcbray@carcosa.net>
Date:   Mon Jun 18 21:33:41 2018 -0400

    Setup for themes and related options
This commit is contained in:
Jason McBrayer 2018-08-28 12:22:20 -04:00
parent db1ed32490
commit 5bdb2de646
24 changed files with 1214 additions and 190 deletions

1
.gitignore vendored
View File

@ -110,3 +110,4 @@ pip-selfcheck.json
/Pipfile.lock
/dev_https
node_modules
/TAGS

View File

@ -1,6 +1,8 @@
from django import forms
from django.conf import settings
from pytz import common_timezones
from .models import Theme, Preference
PRIVACY_CHOICES = (('public', 'Public'),
('unlisted', 'Unlisted'),
@ -14,36 +16,17 @@ MAX_LENGTH = settings.TOOT_MAX_LENGTH
class LoginForm(forms.Form):
instance = forms.CharField(label="Instance",
max_length=256)
username = forms.CharField(label="Email",
max_length=256)
email = forms.EmailField(label="Email")
password = forms.CharField(widget=forms.PasswordInput())
class OAuthLoginForm(forms.Form):
instance = forms.CharField(label="Instance",
max_length=256)
class SettingsForm(forms.Form):
fullbrutalism = forms.BooleanField(label="Use FULLBRUTALISM mode?",
required=False,
help_text=
"""FULLBRUTALISM mode strips away most of the niceties of modern web design when
brutaldon is viewed in a graphical browser. It has no effect in text-only browsers.""")
filter_replies = forms.BooleanField(label="Filter replies from home timeline?",
required=False,
help_text=
"""Should replies be filtered out of your home timeline, giving you only pure,
top-level posts?""")
filter_boosts = forms.BooleanField(label="Filter boosts from home timeline?",
required=False,
help_text=
"""Should replies be filtered out of your home timeline, giving you only pure,
Original Content?""")
timezone = forms.ChoiceField(label="Your local timezone",
choices=timezones,
required=False,
help_text=
"""What time zone do you prefer to have times displayed in? The default choice is UTC.""")
class PreferencesForm(forms.ModelForm):
class Meta:
model = Preference
fields = ['theme', 'filter_replies', 'filter_boosts', 'timezone']
class PostForm(forms.Form):
"""def status_post(self, status, in_reply_to_id=None, media_ids=None,

View File

@ -0,0 +1,42 @@
# Generated by Django 2.0.6 on 2018-06-19 01:03
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0004_auto_20180424_1424'),
]
operations = [
migrations.CreateModel(
name='Preference',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data_saver', models.BooleanField(default=False)),
('fix_emojos', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='Theme',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField(max_length=80)),
('main_css', models.TextField(max_length=1024)),
('tweaks_css', models.TextField(blank=True, max_length=1024, null=True)),
('is_brutalist', models.BooleanField(default=False)),
],
),
migrations.AddField(
model_name='preference',
name='theme',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='brutaldon.Theme'),
),
migrations.AddField(
model_name='account',
name='preferences',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='brutaldon.Preference'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-06-19 01:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0005_auto_20180618_2103'),
]
operations = [
migrations.AlterField(
model_name='theme',
name='main_css',
field=models.TextField(blank=True, default='css/fullbrutalism.css', max_length=1024, null=True),
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 2.0.6 on 2018-06-19 01:03
from django.db import migrations
def set_up_default_themes(apps, schema_editor):
Theme = apps.get_model('brutaldon', 'Theme')
default = Theme(name="default",
main_css="css/bulma.min.css",
tweaks_css="css/brutaldon.css",
is_brutalist=False)
default.save()
dark = Theme(name="default dark",
main_css="css/bulmaswatch-darkly.min.css",
tweaks_css="css/brutaldon-dark.css",
is_brutalist=False)
dark.save()
brutalism = Theme(name="FULLBRUTALISM",
main_css="css/fullbrutalism.css",
is_brutalist=True)
brutalism.save()
large = Theme(name="Minimalist Large", main_css="css/minimal-large.css",
is_brutalist=True)
large.save()
vt240 = Theme(name="vt240 amber", main_css="css/vt240don-amber.css",
is_brutalist=True)
vt240.save()
vt240_green = Theme(name="vt240 green", main_css="css/vt240don-green.css",
is_brutalist=True)
vt240_green.save()
minimal = Theme(name="No styling at all", main_css=None, is_brutalist=True)
minimal.save()
def delete_themes(apps, schema_editor):
Theme = apps.get_model('brutaldon' 'Theme')
for theme in Theme.objects.all():
theme.delete()
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0006_auto_20180618_2112'),
]
operations = [
migrations.RunPython(set_up_default_themes, delete_themes)
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-06-19 01:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0007_auto_20180618_2115'),
]
operations = [
migrations.AlterField(
model_name='theme',
name='name',
field=models.TextField(max_length=80, unique=True),
),
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1 on 2018-08-26 22:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0011_auto_20180825_1017'),
]
operations = [
migrations.AlterField(
model_name='account',
name='username',
field=models.EmailField(max_length=254, unique=True),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.1 on 2018-08-26 23:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0012_auto_20180826_1853'),
]
operations = [
migrations.AlterField(
model_name='preference',
name='theme',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='brutaldon.Theme'),
),
]

View File

@ -1,17 +1,41 @@
from django.db import models
from django.conf import settings
from pytz import common_timezones
timezones = [(tz, tz) for tz in common_timezones]
class Client(models.Model):
name = models.TextField(default = "brutaldon")
name = models.CharField(default = "brutaldon", max_length=80)
api_base_id = models.URLField(default="https://mastodon.social")
client_id = models.TextField(null=True, blank=True)
client_secret = models.TextField(null=True, blank=True)
client_id = models.CharField(null=True, blank=True, max_length=2048)
client_secret = models.CharField(null=True, blank=True, max_length=2048)
def __str__(self):
return self.name + ": " + self.api_base_id
class Theme(models.Model):
name = models.CharField(max_length=80, unique=True)
prefix = models.CharField(max_length=40, null=True, default="default")
main_css = models.CharField(max_length=1024, blank=True, null=True,
default="css/fullbrutalism.css")
tweaks_css = models.CharField(max_length=1024, blank=True, null=True)
is_brutalist = models.BooleanField(default=False)
def __str__(self):
return self.name
class Preference(models.Model):
theme = models.ForeignKey(Theme, models.CASCADE, null=False, default=1)
filter_replies = models.BooleanField(default=False)
filter_boosts = models.BooleanField(default=False)
timezone = models.CharField(max_length=80, blank=True, null=True,
choices=timezones, default='UTC')
class Account(models.Model):
username = models.EmailField()
username = models.EmailField(unique=True)
email = models.EmailField(null=True, blank=True)
django_user = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, null=True)
access_token = models.TextField(null=True, blank=True)
access_token = models.CharField(null=True, blank=True, max_length=2048)
client= models.ForeignKey(Client, models.SET_NULL, null=True)
preferences = models.ForeignKey(Preference, models.SET_NULL, null=True)

View File

@ -0,0 +1,109 @@
body.has-navbar-fixed-top {
padding-top: 48px;
}
body > section > div.container {
max-width: 90ex;
}
.reblog-icon {
position: relative;
top: -24px;
left: 40px;
}
img.fav-avatar {
display: inline;
}
.media {
border-radius: 5px;
-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
padding: 1.25rem;
margin-bottom: 0.75rem;
margin-top: 0.75rem;
}
.media-content {
padding: 1.5ex;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
figure.media-left p.image a img
{
border-radius: 5px;
}
img.avatar
{
border-radius: 5px;
}
.active-context {
background-color: #444;
}
h2.subtitle
{
margin-top: 2rem;
margin-bottom: 1rem;
}
article.media.user-info .content img
{
max-height: 1.5rem;
max-width: 1.5rem;
}
span.account-locked
{
margin-top: 48px;
margin-left: -16px;
}
.errorlist
{
color: #FF0000;
}
.emoji-box
{
padding: .75rem;
background-color: #444;
border: 1px solid #ccc;
border-radius: 5px;
}
img.emoji
{
display: inline;
max-height: 1.5em;
max-width: 1.5em;
vertical-align: text-bottom;
}
emoji-link
{
font-size: 2em;
}
@media screen and (max-width: 768px) {
.media {
display: block;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,230 @@
body, input, textarea, select {
font-family: sans-serif;
background-color: #FAFAFA;
color: #000;
margin: 1em;
font-size: larger;
}
input[text], textarea
{
margin: 0 auto;
position: relative;
width: 100%;
max-width: 120em;
}
a {
color: blue;
text-decoration: underline;
}
a:active {
color: red;
text-decoration: underline;
}
a:visited {
color: purple;
text-decoration: underline;
}
img, img.is-32x32 {
float: left;
max-width: 512px;
max-height: auto;
margin: 4px;
}
.container {
margin: 0 auto;
position: relative;
}
@media screen and (min-width: 1024px) {
.container {
max-width: 960px;
width: 960px;
}
.container.is-fluid {
margin-left: 64px;
margin-right: 64px;
max-width: none;
width: auto;
}
.navbar,
.navbar-menu,
.navbar-start,
.navbar-end {
align-items: stretch;
display: flex;
}
.navbar-start {
justify-content: flex-start;
margin-right: auto;
}
.navbar-end {
justify-content: flex-end;
margin-left: auto;
}
}
@media screen and (max-width: 1279px) {
.container.is-widescreen {
max-width: 1152px;
width: auto;
}
}
@media screen and (max-width: 1471px) {
.container.is-fullhd {
max-width: 1344px;
width: auto;
}
}
@media screen and (min-width: 1280px) {
.container {
max-width: 1152px;
width: 1152px;
}
}
@media screen and (min-width: 1472px) {
.container {
max-width: 1344px;
width: 1344px;
}
}
body > section > div.container {
max-width: 90ex;
}
.level {
clear: both;
}
.title {
font-size: 3ex;
font-weight: bold;
margin-top: 1ex;
margin-bottom: 1ex;
}
.subtitle {
font-size: 1.5ex;
font-weight: bold;
margin-top: 0.25ex;
margin-bottom: 0.25ex;
}
.toot {
clear: both;
}
.image.is-32x32, .is-32x32 img, img.is-32x32 {
width: 32px;
height: 32px;
}
.image.is-48x48, .is-48x48 img, img.is-48x48 {
width: 48px;
height: 48px;
}
.image.is-64x64, .is-64x64 img, img.is-64x64 {
width: 64px;
height: 64px;
}
.image.is-96x96, .is-96x96 img, img.is-96x96 {
width: 96px;
height: 96px;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
.media {
padding: 1ex;
margin: 4px;
overflow: auto;
}
.media.active-context {
background-color: #DDD;
}
.field
{
margin-top: 1em;
}
label
{
font-weight: bold;
}
.control, .select
{
margin-top: 0.5ex;
margin-bottom: 0.5ex;
}
.account-avatar
{
display: inline-block;
}
.reblog-icon
{
margin-top: 32px;
display: inline-block;
}
.media-content
{
margin-top: 1ex;
}
.media-content > div > p
{
margin-bottom: 1ex;
}
.textarea
{
max-width: 100%;
}
.errorlist
{
color: #FF0000;
}
img.emoji
{
display: inline;
max-height: 1.5rem;
max-width: 1.5rem;
vertical-align: text-bottom;
}
hr.is-hidden
{
display: none;
}
.box
{
border-radius: 5px;
border: 1px solid #000;
padding: 1.5em;
margin-bottom: 1.5em;
}

View File

@ -0,0 +1,184 @@
html, a, div, div.notification, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, menu, nav, section, time, mark, audio, video, details, summary, h1.title, h2.subtitle {
font-family: "monospace";
color: #ff7700;
background-color: #000000;
font-size: medium;
}
tr, td, ul, ol {
border-color: #ff7700;
outline: 1px solid #ff7700;
padding-top: 5px;
padding-bottom: 5px;
margin-top: 5px;
margin-bottom: 10px;
}
a, a.button, a.button.is-primary, input.button.is-primary, input, .input, .textarea, .footer, .label, select, textarea {
color: #ff8800;
border-color: #ff8800;
background-color: #000000;
font-weight: bolder;
text-decoration-line: none;
font-size: medium;
}
a:hover, a.button:hover, a.button.is-primary:hover, input.button.is-primary:hover {
color: #000000;
background-color: #ff8800;
}
.control input[type=text], .control textarea
{
width: 100%;
max-width: 80em;
border: 1px solid #ff7700;
margin-bottom: 1ex;
}
.control input, .control select
{
border: 1px solid #ff7700;
background-color: #000000;
margin-bottom: 1ex;
}
img {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
}
div.card-header-title, div.card-header-icon {
color: #ff7700;
-webkit-text-stroke: 2px black;
-moz-text-stroke: 2px black;
text-stroke: 2px black;
}
.container {
margin: 0 auto;
max-width: 80em;
}
.box {
padding: 1em;
border-color: #ff7700;
outline: 1px solid #ff7700;
background-color: #000;
margin-bottom: 1em;
}
hr.is-hidden {
display: none;
}
.image.is-32x32, .is-32x32 img, img.is-32x32 {
width: 32px;
height: 32px;
}
.image.is-48x48, .is-48x48 img, img.is-48x48 {
width: 48px;
height: 48px;
}
.image.is-64x64, .is-64x64 img, img.is-64x64 {
width: 64px;
height: 64px;
}
.image.is-96x96, .is-96x96 img, img.is-96x96 {
width: 96px;
height: 96px;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
.media {
background-color: black;
border-radius: 5px;
/*-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);*/
color: #4a4a00;
padding: 1.25rem;
margin-bottom: 0.75rem;
margin-top: 0.75rem;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
figure.media-left p.image a img
{
border-radius: 5px;
}
.active-context {
background-color: #000000;
border-color: #ffcc00;
outline: 1px solid #ffcc00;
}
.account-avatar
{
display: inline-block;
}
.reblog-icon
{
margin-top: 32px;
display: inline-block;
}
img.fav-avatar {
display: inline;
}
.level {
display: block;
margin-bottom: 1ex;
margin-top: 1ex;
}
@media screen and (max-width: 768px) {
.media {
display: block;
}
}
@media screen and (min-width: 1024px) {
.navbar,
.navbar-menu,
.navbar-start,
.navbar-end {
align-items: stretch;
display: flex;
}
.navbar-start {
justify-content: flex-start;
margin-right: auto;
}
.navbar-end {
justify-content: flex-end;
margin-left: auto;
}
}

View File

@ -0,0 +1,188 @@
html, body, a, div, div.notification, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, menu, nav, section, time, mark, audio, video, details, summary, h1.title, h2.subtitle {
font-family: "monospace";
color: #00ff77;
background-color: #000;
font-size: medium;
}
tr, td, ul, ol {
border-color: #00ff77;
outline: 1px solid #00ff77;
padding-top: 5px;
padding-bottom: 5px;
margin-top: 5px;
margin-bottom: 10px;
background-color: #000000;
}
a, a.button, a.button.is-primary, input.button.is-primary, input, .input, .textarea, .footer, .label, select, textarea {
color: #00ff88;
border-color: #00ff88;
background-color: #000000;
font-weight: bolder;
text-decoration-line: none;
font-size: medium;
}
a:hover, a.button:hover, a.button.is-primary:hover, input.button.is-primary:hover {
color: #000000;
background-color: #00ff88;
}
.control input[type=text], .control textarea
{
width: 100%;
max-width: 80em;
border: 1px solid #00ff77;
background-color: #000000;
margin-bottom: 1ex;
}
.control input, .control select
{
border: 1px solid #00ff77;
background-color: #000000;
margin-bottom: 1ex;
}
img {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
}
div.card-header-title, div.card-header-icon {
color: #00ff77;
-webkit-text-stroke: 2px black;
-moz-text-stroke: 2px black;
text-stroke: 2px black;
}
.container {
margin: 0 auto;
max-width: 80em;
color: #00ff88;
background-color: #000000;
}
.box {
padding: 1em;
border-color: #00ff77;
outline: 1px solid #00ff77;
background-color: #000;
margin-bottom: 1em;
}
hr.is-hidden {
display: none;
}
.image.is-32x32, .is-32x32 img, img.is-32x32 {
width: 32px;
height: 32px;
}
.image.is-48x48, .is-48x48 img, img.is-48x48 {
width: 48px;
height: 48px;
}
.image.is-64x64, .is-64x64 img, img.is-64x64 {
width: 64px;
height: 64px;
}
.image.is-96x96, .is-96x96 img, img.is-96x96 {
width: 96px;
height: 96px;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
.media {
background-color: black;
border-radius: 5px;
/*-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);*/
color: #4a4a00;
padding: 1.25rem;
margin-bottom: 0.75rem;
margin-top: 0.75rem;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
figure.media-left p.image a img
{
border-radius: 5px;
}
.active-context {
background-color: #000000;
border-color: #00ff88;
outline: 1px solid #00ff88;
}
.account-avatar
{
display: inline-block;
}
.reblog-icon
{
margin-top: 32px;
display: inline-block;
}
img.fav-avatar {
display: inline;
}
.level {
display: block;
margin-bottom: 1ex;
margin-top: 1ex;
}
@media screen and (max-width: 768px) {
.media {
display: block;
}
}
@media screen and (min-width: 1024px) {
.navbar,
.navbar-menu,
.navbar-start,
.navbar-end {
align-items: stretch;
display: flex;
}
.navbar-start {
justify-content: flex-start;
margin-right: auto;
}
.navbar-end {
justify-content: flex-end;
margin-left: auto;
}
}

View File

@ -13,16 +13,22 @@
brutaldon
{% endif %}
{% endblock %}</title>
{% if not fullbrutalism %}
{% if not preferences %}
<link rel="stylesheet"
href="{% static 'css/bulma.min.css' %}">
<link rel="stylesheet"
href="{% static 'css/fork-awesome.min.css' %}">
<link rel="stylesheet" href="{% static "css/brutaldon.css" %}">
{% else %}
<link rel="stylesheet"
href="{% static preferences.theme.main_css %}">
<link rel="stylesheet" href="{% static preferences.theme.tweaks_css %}">
{% if not preferences.theme.is_brutalist %}
<link rel="stylesheet"
href="{% static 'css/fork-awesome.min.css' %}">
{% block page_scripts %}
{% endblock %}
{% else %}
<link rel="stylesheet" href="{% static "css/fullbrutalism.css" %}" >
{% endif %}
{% endif %}
{% if own_acct %}
<link rel="icon" href="{{ own_acct.avatar_static }}">
@ -32,7 +38,7 @@
</head>
<body class="has-navbar-fixed-top">
{% block navbar %}
<nav class="navbar is-fixed-top is-light" role="navigation"
<nav class="navbar is-fixed-top" role="navigation"
aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="{% url "home" %}">
@ -115,7 +121,7 @@
</a>
</div>
<div class="level-right">
{% if fullbrutalism %}
{% if preferences.theme.is_brutalist %}
<img class="level-item" src="{% static '/images/lynx.gif' %}"
alt="Lynx Now!">
<img class="level-item" src="{% static '/images/now9.gif' %}"
@ -128,7 +134,7 @@
</div>
</footer>
{% if not fullbrutalism %}
{% if not preferences.theme.is_brutalist %}
<script type="application/javascript">
document.addEventListener('DOMContentLoaded', function () {

View File

@ -128,7 +128,7 @@
</div>
</form>
{% if not fullbrutalism %}
{% if not preferences.theme.is_brutalist %}
<script type="application/javascript">
var file1 = document.getElementById("id_media_file_1");
file1.onchange = function(){

View File

@ -5,9 +5,9 @@
{% load static %}
{% if active %}
<article class="media active-context">
<article class="media box active-context">
{% else %}
<article class="media">
<article class="media box">
{% endif %}
<figure class="media-left">

View File

@ -10,7 +10,7 @@ Brutaldon ({{ own_acct.username }}) - {{ user.acct }} timelime
{% block content %}
<div class="card">
{% if not fullbrutalism %}
{% if not preferences.theme.is_brutalist %}
<div class="card-header" style="background-image: url({{ user.header }});">
{% else %}
<div class="card-header">

View File

@ -17,9 +17,9 @@
</div>
<div class="field">
<label class="label">{{ form.username.label }}</label>
<label class="label">{{ form.email.label }}</label>
<div class="control has-icons-left">
{% render_field form.username class+="input" %}
{% render_field form.email class+="input" %}
<span class="icon is-small is-left">
<i class="fa fa-user"></i>
</span>

View File

@ -2,15 +2,22 @@
{% load widget_tweaks %}
{% block content %}
<div class="container">
<h1 class="title">Settings</h1>
<form method="post" action="{% url "settings" %}" >
{% csrf_token %}
<div class="field">
<label class="label checkbox">
{% render_field form.fullbrutalism %}
{{ form.fullbrutalism.label }}
</label>
</div>
<div class="field">
<label class="label" for="theme">{{ form.theme.label }}</label>
<div class="control has-icons-left">
<div class="select">
{% render_field form.theme class+="select" %}
<span class="icon is-small is-left">
<span class="fa fa-paint-brush"></span>
</div>
</div>
</div>
<div class="field">
<label class="label checkbox">
{% render_field form.filter_replies %}
@ -38,4 +45,5 @@
value="Save" class="button is-primary" >
</div>
</form>
</div>
{% endblock %}

View File

@ -5,12 +5,11 @@ from django.urls import reverse
from django.views.decorators.cache import never_cache, cache_page
from django.urls import reverse
from django.core.files.uploadhandler import TemporaryFileUploadHandler
from brutaldon.forms import LoginForm, OAuthLoginForm, SettingsForm, PostForm
from brutaldon.models import Client, Account
from brutaldon.forms import LoginForm, OAuthLoginForm, PreferencesForm, PostForm
from brutaldon.models import Client, Account, Preference, Theme
from mastodon import Mastodon, AttribAccessDict, MastodonError
from urllib import parse
from pdb import set_trace
from bs4 import BeautifulSoup
class NotLoggedInException(Exception):
pass
@ -22,52 +21,26 @@ class Singleton(type):
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MastodonPool(dict, metaclass=Singleton):
pass
def get_mastodon(request):
pool = MastodonPool()
if request.session.has_key('access_token'):
try:
client = Client.objects.get(api_base_id=request.session['instance'])
except (Client.DoesNotExist, Client.MultipleObjectsReturned):
raise NotLoggedInException()
if request.session['access_token'] in pool.keys():
mastodon = pool[request.session['access_token']]
else:
mastodon = Mastodon(
client_id = client.client_id,
client_secret = client.client_secret,
api_base_url = client.api_base_id,
access_token = request.session['access_token'],
ratelimit_method='throw')
pool[request.session['access_token']] = mastodon
else:
def get_usercontext(request):
if is_logged_in(request):
try:
client = Client.objects.get(api_base_id=request.session['instance'])
user = Account.objects.get(username=request.session['username'])
except (Client.DoesNotExist, Client.MultipleObjectsReturned,
Account.DoesNotExist, Account.MultipleObjectsReturned):
raise NotLoggedInException()
if user.access_token in pool.keys():
mastodon = pool[user.access_token]
else:
mastodon = Mastodon(
client_id = client.client_id,
client_secret = client.client_secret,
access_token = user.access_token,
api_base_url = client.api_base_id,
ratelimit_method="throw")
pool[user.access_token] = mastodon
return mastodon
def fullbrutalism_p(request):
return request.session.get('fullbrutalism', False)
return user, mastodon
else:
return None, None
def is_logged_in(request):
return (request.session.has_key('instance') and
(request.session.has_key('username') or
request.session.has_key('access_token')))
return request.session.has_key('user')
def br_login_required(function=None, home_url=None, redirect_field_name=None):
"""Check that the user is logged in to a Mastodon instance.
@ -107,7 +80,7 @@ def br_login_required(function=None, home_url=None, redirect_field_name=None):
return _dec(function)
def timeline(request, timeline='home', timeline_name='Home', max_id=None, since_id=None):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
data = mastodon.timeline(timeline, limit=100, max_id=max_id, since_id=since_id)
form = PostForm(initial={'visibility': request.session['user'].source.privacy})
try:
@ -122,15 +95,15 @@ def timeline(request, timeline='home', timeline_name='Home', max_id=None, since_
next = None
# This filtering has to be done *after* getting next/prev links
if request.session.get('filter_replies', False):
if account.preferences.filter_replies:
data = [x for x in data if not x.in_reply_to_id]
if request.session.get('filter_boosts', False):
if account.preferences.filter_boosts:
data = [x for x in data if not x.reblog]
return render(request, 'main/%s_timeline.html' % timeline,
{'toots': data, 'form': form, 'timeline': timeline,
'timeline_name': timeline_name,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request),
'preferences': account.preferences,
'prev': prev, 'next': next})
@br_login_required
@ -148,14 +121,14 @@ def fed(request, next=None, prev=None):
@br_login_required
def tag(request, tag):
try:
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
except NotLoggedInException:
return redirect(login)
data = mastodon.timeline_hashtag(tag)
return render(request, 'main/timeline.html',
{'toots': data, 'timeline_name': '#'+tag,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
@never_cache
def login(request):
@ -219,7 +192,27 @@ def oauth_callback(request):
scopes=['read', 'write', 'follow'])
request.session['access_token'] = access_token
user = mastodon.account_verify_credentials()
try:
account = Account.objects.get(username=user.username)
account.access_token = access_token
if not account.preferences:
preferences = Preference(theme = Theme.objects.get(id=1))
preferences.save()
account.preferences = preferences
else:
request.session['timezone'] = account.preferences.timezone
account.save()
except (Account.DoesNotExist, Account.MultipleObjectsReturned):
preferences = Preference(theme = Theme.objects.get(id=1))
preferences.save()
account = Account(username=user.acct,
access_token = access_token,
client = Client.objects.get(api_base_id=request.session['instance']),
preferences = preferences)
request.session['user'] = user
request.session['username'] = user.username
account.username = user.username
account.save()
return redirect(home)
@ -240,7 +233,7 @@ def old_login(request):
api_base_url = api_base_url.lower()
request.session['instance'] = api_base_url
username = form.cleaned_data['username']
email = form.cleaned_data['email']
password = form.cleaned_data['password']
try:
@ -261,26 +254,30 @@ def old_login(request):
api_base_url = api_base_url)
try:
account = Account.objects.get(username=username, client_id=client.id)
account = Account.objects.get(email=email, client_id=client.id)
except (Account.DoesNotExist, Account.MultipleObjectsReturned):
preferences = Preference(theme = Theme.objects.get(id=1))
preferences.save()
account = Account(
username = username,
email = email,
access_token = "",
client = client)
client = client,
preferences = preferences)
try:
access_token = mastodon.log_in(username,
password,
scopes=['read', 'write', 'follow'])
account.access_token = access_token
account.save()
request.session['username'] = username
user = mastodon.account_verify_credentials()
request.session['user'] = user
request.session['username'] = user.username
account.username = user.username
request.session['timezone'] = account.preferences.timezone;
account.save()
return redirect(home)
except Exception as ex:
form.add_error('', ex)
return render(request, 'setup/login.html', {'form': form})
else:
return render(request, 'setup/login.html', {'form': form})
@ -296,7 +293,7 @@ def error(request):
@br_login_required
def note(request, next=None, prev=None):
try:
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
except NotLoggedInException:
return redirect(about)
notes = mastodon.notifications(limit=100, max_id=next, since_id=prev)
@ -314,23 +311,23 @@ def note(request, next=None, prev=None):
{'notes': notes,'timeline': 'Notifications',
'timeline_name': 'Notifications',
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request),
'preferences': account.preferences,
'prev': prev, 'next': next})
@br_login_required
def thread(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
context = mastodon.status_context(id)
toot = mastodon.status(id)
return render(request, 'main/thread.html',
{'context': context, 'toot': toot,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
@br_login_required
def user(request, username, prev=None, next=None):
try:
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
except NotLoggedInException:
return redirect(about)
try:
@ -354,41 +351,40 @@ def user(request, username, prev=None, next=None):
{'toots': data, 'user': user_dict,
'relationship': relationship,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request),
'preferences': account.preferences,
'prev': prev, 'next': next})
@never_cache
@br_login_required
def settings(request):
account = Account.objects.get(username=request.session['username'])
if request.method == 'POST':
form = SettingsForm(request.POST)
form = PreferencesForm(request.POST)
if form.is_valid():
request.session['fullbrutalism'] = form.cleaned_data['fullbrutalism']
request.session['filter_replies'] = form.cleaned_data['filter_replies']
request.session['filter_boosts'] = form.cleaned_data['filter_boosts']
request.session['timezone'] = form.cleaned_data['timezone']
account.preferences.theme =form.cleaned_data['theme']
account.preferences.filter_replies = form.cleaned_data['filter_replies']
account.preferences.filter_boosts = form.cleaned_data['filter_boosts']
account.preferences.timezone = form.cleaned_data['timezone']
request.session['timezone'] = account.preferences.timezone
account.preferences.save()
account.save()
return redirect(home)
else:
return render(request, 'setup/settings.html',
{'form' : form,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
{'form' : form, 'account': account})
else:
form = SettingsForm(initial={
"fullbrutalism": fullbrutalism_p(request),
"filter_replies": request.session.get('filter_replies', False),
"filter_boosts": request.session.get('filter_boosts', False),
"timezone": request.session.get('timezone', 'UTC')
})
request.session['timezone'] = account.preferences.timezone
form = PreferencesForm(instance=account.preferences)
return render(request, 'setup/settings.html',
{ 'form': form,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'account': account,
'preferences': account.preferences})
@never_cache
@br_login_required
def toot(request, mention=None):
account, mastodon = get_usercontext(request)
if request.method == 'GET':
if mention:
if not mention.startswith('@'):
@ -400,12 +396,10 @@ def toot(request, mention=None):
return render(request, 'main/post.html',
{'form': form,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
elif request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
mastodon = get_mastodon(request)
# create media objects
media_objects = []
for index in range(1,5):
@ -427,14 +421,14 @@ def toot(request, mention=None):
return render(request, 'main/post.html',
{'form': form,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
else:
return redirect(toot)
@br_login_required
def redraft(request, id):
if request.method == 'GET':
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
toot_content = BeautifulSoup(toot.content).get_text("\n")
form = PostForm({'status': toot_content,
@ -448,10 +442,10 @@ def redraft(request, id):
return render(request, 'main/redraft.html',
{'toot': toot, 'form': form, 'redraft':True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
elif request.method == 'POST':
form = PostForm(request.POST, request.FILES)
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
if form.is_valid():
media_objects = []
@ -476,7 +470,7 @@ def redraft(request, id):
return render(request, 'main/redraft.html',
{'toot': toot, 'form': form, 'redraft': True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
else:
return redirect(redraft, id)
@ -495,7 +489,7 @@ def safe_get_attachment(toot, index):
@br_login_required
def reply(request, id):
if request.method == 'GET':
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
context = mastodon.status_context(id)
if toot.account.acct != request.session['user'].acct:
@ -512,10 +506,10 @@ def reply(request, id):
return render(request, 'main/reply.html',
{'context': context, 'toot': toot, 'form': form, 'reply':True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
elif request.method == 'POST':
form = PostForm(request.POST, request.FILES)
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
if form.is_valid():
# create media objects
media_objects = []
@ -539,14 +533,14 @@ def reply(request, id):
return render(request, 'main/reply.html',
{'context': context, 'toot': toot, 'form': form, 'reply': True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
else:
return redirect(reply, id)
@never_cache
@br_login_required
def fav(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
if request.method == 'POST':
if not request.POST.get('cancel', None):
@ -560,12 +554,12 @@ def fav(request, id):
{"toot": toot,
'own_acct': request.session['user'],
"confirm_page": True,
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
@never_cache
@br_login_required
def boost(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
if request.method == 'POST':
if not request.POST.get('cancel', None):
@ -579,12 +573,12 @@ def boost(request, id):
{"toot": toot,
'own_acct': request.session['user'],
'confirm_page': True,
"fullbrutalism": fullbrutalism_p(request)})
"preferences": account.preferences})
@never_cache
@br_login_required
def delete(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
if request.method == 'POST':
if toot.account.acct != request.session['user'].acct:
@ -597,12 +591,12 @@ def delete(request, id):
{"toot": toot,
'own_acct': request.session['user'],
'confirm_page': True,
"fullbrutalism": fullbrutalism_p(request)})
"preferences": account.preferences})
@never_cache
@br_login_required
def follow(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
try:
user_dict = mastodon.account(id)
relationship = mastodon.account_relationships(user_dict.id)[0]
@ -620,12 +614,12 @@ def follow(request, id):
{"user": user_dict, "relationship": relationship,
"confirm_page": True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
@never_cache
@br_login_required
def block(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
try:
user_dict = mastodon.account(id)
relationship = mastodon.account_relationships(user_dict.id)[0]
@ -643,12 +637,12 @@ def block(request, id):
{"user": user_dict, "relationship": relationship,
"confirm_page": True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
@never_cache
@br_login_required
def mute(request, id):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
try:
user_dict = mastodon.account(id)
relationship = mastodon.account_relationships(user_dict.id)[0]
@ -666,12 +660,13 @@ def mute(request, id):
{"user": user_dict, "relationship": relationship,
"confirm_page": True,
'own_acct': request.session['user'],
'fullbrutalism': fullbrutalism_p(request)})
'preferences': account.preferences})
@br_login_required
def search(request):
account, mastodon = get_usercontext(request)
return render(request, 'main/search.html',
{"fullbrutalism": fullbrutalism_p(request),
{"preferences": account.preferences,
'own_acct': request.session['user'],
})
@ -683,31 +678,37 @@ def search_results(request):
query = request.POST.get('q', '')
else:
query = ''
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
results = mastodon.search(query)
return render(request, 'main/search_results.html',
{"results": results,
'own_acct': request.session['user'],
"fullbrutalism": fullbrutalism_p(request)})
"preferences": account.preferences})
def about(request):
version = django_settings.BRUTALDON_VERSION
account, mastodon = get_usercontext(request)
if account:
preferences = account.preferences
else:
preferences = None
return render(request, 'about.html',
{"fullbrutalism": fullbrutalism_p(request),
{"preferences": preferences,
"version": version,
'own_acct': request.session.get('user', None),
})
def privacy(request):
account, mastodon = get_usercontext(request)
return render(request, 'privacy.html',
{"fullbrutalism": fullbrutalism_p(request),
{"preferences": preferences,
'own_acct' : request.session.get('user', None)})
@cache_page(60 * 30)
@br_login_required
def emoji_reference(request):
mastodon = get_mastodon(request)
account, mastodon = get_usercontext(request)
emojos = mastodon.custom_emojis()
return render(request, 'main/emoji.html',
{"fullbrutalism": fullbrutalism_p(request),
{"preferences": account.preferences,
"emojos": sorted(emojos, key=lambda x: x['shortcode']),
'own_acct' : request.session['user']})