File sharing update (requires migrations for existing databases)

There are a bunch of migration files that have been thrown into the repository. Sadly, migrating is a huge pain in the fucking ass, you may need to rollback your database or god knows what.

- did not add change logs too lazy
This commit is contained in:
some weird guy
2023-10-16 15:22:41 -07:00
parent d49ce565ee
commit e5c99e0248
26 changed files with 942 additions and 1070 deletions
+4 -6
View File
@@ -4,7 +4,10 @@ Stuff that is done:
- Hide email password reset form if no SMTP server is set up. (done by Arian Kordi)
- We are now using django groups.
- Admins have a better interface.
- Post limit shows publicly, with indication on how many posts are left.
- Post limit shows for admins, with indication on how many posts are left.
- Image and video file boxes in one.
- Full ImageField integration.
Stuff that is in progress
- New Warning system.
@@ -17,18 +20,13 @@ Todo:
When banning someone, add the option to ban or revoke invites for whoever invited that person.
- FIX THE FUCKING CONTACT PAGE (nah not doing that lmao)
- also, CONTINUE MOVING OVER TO FORMS.PY
Profile settings
Email reset form
Post forms
Signup page
A bunch of other forms I haven't thought about.
Ideas:
- An account approval system, we really need one. "is_new" exists in the user model, we just have to put that to use.
- "Muting" feature to replace "active"
- Image and video file boxes in one.
- Make it, so you need to enter your current password to change your email address.
- Ways for mods to view who invited who easily.
- Full ImageField integration.
- Filefield may have to be used for both photos and videos.
- remove the useless feedback thing. (You can just make a bug reporting community)
+5 -1
View File
@@ -1,6 +1,9 @@
# About
Cedar-Django is a fork of Closedverse with custom features added to it.
### Upload anything you want.
You can upload whatever files you want to the site. The primary supported file types are Images, Videos and audio files. Other files will have a download button.
### Theme changing
Just like in Indigo, you can change the color of your theme. While this is only visible to each user specifically, a global theme can be set in Settings.py.
@@ -33,6 +36,7 @@ Moderators and staff will be able to revoke a user's ability to add new users if
- Django 3.2.2
- urllib3
- lxml
- django-cleanup
- passlib
- bcrypt
- pillow
@@ -59,7 +63,7 @@ You need Pip
4.
Get everything else you need.
`pip3 install Django==3.2.2 urllib3 lxml passlib bcrypt pillow django-markdown-deux django-markdown2 whitenoise django-xff`
`pip3 install Django==3.2.2 urllib3 lxml passlib bcrypt pillow django-markdown-deux django-markdown2 whitenoise django-xff django-cleanup`
5.
Clone the clone!
+7 -12
View File
@@ -29,7 +29,7 @@ DEBUG = False
# Do not include 127.0.0.1 or localhost
# in production for security reasons.
ALLOWED_HOSTS = [
'',
'127.0.0.1',
]
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
@@ -48,6 +48,8 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'markdown_deux',
'closedverse_main',
# Comment this out to disable file cleanup.
'django_cleanup.apps.CleanupConfig',
]
X_FRAME_OPTIONS='SAMEORIGIN'
@@ -229,12 +231,11 @@ LOGIN_EXEMPT_URLS = {
r'^help/rules$',
r'^help/contact$',
r'^help/login$',
r'^s/.*$',
}
# Action to perform on images belonging to posts/
# comments when they are deleted
# 0: keep, 1: move to 'rm' folder, 2: delete
IMAGE_DELETE_SETTING = 2
# The max file size allowed. (default: 10*1024*1024")
max_file_size = 10*1024*1024
# allow sign ups.
allow_signups = True
@@ -253,14 +254,8 @@ level_needed_to_man_communities = 5
# if someone's level is equal or above this, they can edit any user with a lower level on your clone.
level_needed_to_man_users = 5
# minimum_password_length is removed as the AUTH_PASSWORD_VALIDATORS work as of 08/2023
# The hard limit for uploading, Will cause an error if this is exceeded. This is set to 15MB by default (15728640)
DATA_UPLOAD_MAX_MEMORY_SIZE = 52428800
# Set the default theme here! Set this to None to use the normal Closedverse theme.
# TODO, make this work for users who aren't logged in.
site_wide_theme_hex = 'ff4159'
site_wide_theme_hex = None
# age (minimal 13 due to C.O.P.P.A)
age_allowed = "13"
+4 -4
View File
@@ -56,7 +56,7 @@ class UserAdmin(BaseUserAdmin):
fieldsets = (
(None, {'fields': ('nickname', 'username', 'password')}),
('Personal info', {'fields': ('email', ('addr', 'signup_addr'))}),
('Cosmetic', {'fields': (('role', 'avatar', 'has_mh'), 'color', 'theme', 'bg_url',)}),
('Cosmetic', {'fields': (('role', 'avatar_type', 'avatar_input', 'avatar_upload'), 'color', 'theme')}),
('Data', {'fields': ('last_login', 'created', 'hide_online')}),
('Permissions', {'fields': (('is_active', 'is_staff', 'is_superuser', 'can_invite'), 'level', 'c_tokens', 'groups', 'user_permissions')}),
)
@@ -132,7 +132,7 @@ class ConversationAdmin(admin.ModelAdmin):
class PostAdmin(admin.ModelAdmin):
raw_id_fields = ('creator', 'poll', 'community', )
search_fields = ('id', 'body', 'creator__username', )
list_display = ('id', 'created', 'creator', 'body', 'is_rm', )
list_display = ('id', 'created', 'creator', 'body', 'file', 'is_rm', )
list_filter = ('is_rm', 'created', )
actions = [Hide_content, Show_content, Disable_comments, Enable_comments]
def get_queryset(self, request):
@@ -141,7 +141,7 @@ class PostAdmin(admin.ModelAdmin):
class CommentAdmin(admin.ModelAdmin):
raw_id_fields = ('creator', 'original_post', 'community', )
search_fields = ('id', 'body', 'creator__username', )
list_display = ('id', 'created', 'creator', 'body', 'original_post', 'is_rm', )
list_display = ('id', 'created', 'creator', 'body', 'file', 'original_post', 'is_rm', )
list_filter = ('is_rm', 'created', )
actions = [Hide_content, Show_content]
def get_queryset(self, request):
@@ -159,7 +159,7 @@ class CommunityAdmin(admin.ModelAdmin):
class MessageAdmin(admin.ModelAdmin):
raw_id_fields = ('creator', 'conversation', )
search_fields = ('id', 'body', 'creator__username', )
list_display = ('id', 'created', 'creator', 'conversation', 'body', 'read', 'is_rm', )
list_display = ('id', 'created', 'creator', 'conversation', 'body', 'file', 'read', 'is_rm', )
list_filter = ('is_rm', 'read', 'created', )
actions = [Hide_content, Show_content]
def get_queryset(self, request):
+254 -35
View File
@@ -7,14 +7,16 @@ from django.core.files.base import ContentFile
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.utils.timezone import timedelta
from django.core.validators import URLValidator
from closedverse import settings
from django.core.validators import EmailValidator
# will be used for community icons and profile pictures
def compress_and_resize_icon(image):
def compress_and_resize_content(image, icon=False):
try:
im = Image.open(image)
im = im.convert('RGB') # Convert to RGB
im = im.convert('RGB')
# Crop to 1:1 aspect ratio
if icon == True:
width, height = im.size
min_dimension = min(width, height)
left = (width - min_dimension) / 2
@@ -22,52 +24,129 @@ def compress_and_resize_icon(image):
right = (width + min_dimension) / 2
bottom = (height + min_dimension) / 2
im = im.crop((left, top, right, bottom))
# Resize to 100 by 100 or smaller
im.thumbnail((100, 100))
# Compress the image
output = io.BytesIO()
im.save(output, format='WEBP', quality=85)
output.seek(0)
random_name = f"{uuid.uuid4()}.webp"
return ContentFile(output.read(), name=random_name)
def compress_and_resize_content(image):
im = Image.open(image)
im = im.convert('RGB')
# Resize to 1,200 by 1,200 or smaller
else:
im.thumbnail((1200, 1200))
# Compress the image
output = io.BytesIO()
im.save(output, format='WEBP', quality=85)
# no more webp
im.save(output, format='JPEG', quality=85)
output.seek(0)
random_name = f"{uuid.uuid4()}.webp"
random_name = f"{uuid.uuid4()}.jpg"
return ContentFile(output.read(), name=random_name)
except:
return image
# I do want to move each and every form over to here. Not only will this trivialize making new forms, this will also make it more secure or something.
class CommunitySettingForm(forms.ModelForm):
class message_form(forms.ModelForm):
body = forms.CharField(max_length=2200, required=True)
file = forms.FileField(required=False)
feeling_id = forms.IntegerField(required=False)
def clean_file(self):
file = self.cleaned_data.get('file')
max_size = settings.max_file_size
if file:
if file.size > max_size:
raise ValidationError(f'File size is too large. ({max_size / 1024 /1024 }MB max)')
if file.content_type.startswith('image'):
return compress_and_resize_content(image=file)
return file
class Meta:
model = Message
fields = (
'body',
'file',
'feeling_id',
)
class comment_form(forms.ModelForm):
body = forms.CharField(max_length=2200, required=True)
file = forms.FileField(required=False)
feeling_id = forms.IntegerField(required=False)
is_spoiler = forms.BooleanField(required=False)
def clean_file(self):
file = self.cleaned_data.get('file')
max_size = settings.max_file_size
if file:
if file.size > max_size:
raise ValidationError(f'File size is too large. ({max_size / 1024 /1024 }MB max)')
if file.content_type.startswith('image'):
return compress_and_resize_content(image=file)
return file
class Meta:
model = Comment
fields = (
'body',
'file',
'feeling_id',
'is_spoiler',
)
class post_form(forms.ModelForm):
body = forms.CharField(max_length=2200, required=True)
url = forms.URLField(required=False)
file = forms.FileField(required=False)
feeling_id = forms.IntegerField(required=False)
is_spoiler = forms.BooleanField(required=False)
def clean_file(self):
file = self.cleaned_data.get('file')
max_size = settings.max_file_size
if file:
if file.size > max_size:
raise ValidationError(f'File size is too large. ({max_size / 1024 /1024 }MB max)')
if file.content_type.startswith('image'):
return compress_and_resize_content(image=file)
return file
def clean_url(self):
url = self.cleaned_data.get('url')
if url:
try:
URLValidator()(value=url)
except ValidationError:
raise ValidationError("Uh-oh, that URL wasn't valid.")
return url
class Meta:
model = Post
fields = (
'body',
'url',
'file',
'feeling_id',
'is_spoiler',
)
class edit_community(forms.ModelForm):
description = forms.CharField(max_length = 2200,required=False, widget=forms.Textarea(attrs={'class': 'textarea'}))
def __init__(self, *args, **kwargs):
super(CommunitySettingForm, self).__init__(*args, **kwargs)
super(edit_community, self).__init__(*args, **kwargs)
# Store the initial values of the image fields
self.initial_ico = self.instance.ico
self.initial_banner = self.instance.banner
def clean_ico(self):
ico = self.cleaned_data.get('ico')
max_size = settings.max_file_size
# Check if the image has changed
if ico and ico != self.initial_ico:
return compress_and_resize_icon(image=ico)
if ico.size > max_size:
raise ValidationError(f'File size is too large. ({max_size / 1024 /1024 }MB max)')
return compress_and_resize_content(image=ico, icon=True)
return ico
def clean_banner(self):
banner = self.cleaned_data.get('banner')
max_size = settings.max_file_size
# Check if the image has changed
if banner and banner != self.initial_banner:
if banner.size > max_size:
raise ValidationError(f'File size is too large. ({max_size / 1024 /1024 }MB max)')
return compress_and_resize_content(image=banner)
return banner
@@ -82,7 +161,7 @@ class CommunitySettingForm(forms.ModelForm):
'banner',
)
class Settomgs_Change_Password(forms.Form):
class set_password(forms.Form):
Old_Password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'auth-input', 'placeholder': 'Old Password'}))
New_Password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'auth-input', 'placeholder': 'New Password'}))
Confirm_Password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'auth-input', 'placeholder': 'Confirm Password'}))
@@ -103,17 +182,17 @@ class Settomgs_Change_Password(forms.Form):
except forms.ValidationError as error:
raise forms.ValidationError("your password fucking sucks!")
return cleaned_data
class PurgeForm(forms.Form):
class purge_user(forms.Form):
purge_posts = forms.BooleanField(required=False, label='Purge all posts', help_text='Purge all posts from this user.')
purge_comments = forms.BooleanField(required=False, label='Purge all comments', help_text='Purge all comments from this user.')
restore_all = forms.BooleanField(required=False, label='Restore purged content', help_text='Restore everything that was purged, this will not apply to posts deleted manually.')
class LoginForm(forms.Form):
class sign_in(forms.Form):
username = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class': 'auth-input', 'placeholder': 'Username / Email'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'auth-input', 'placeholder': 'Password'}))
def clean(self):
cleaned_data = super(LoginForm, self).clean()
cleaned_data = super(sign_in, self).clean()
username = cleaned_data.get('username')
password = cleaned_data.get('password')
@@ -131,9 +210,10 @@ class LoginForm(forms.Form):
active_user_ban = Ban.objects.filter(to=user[0], active=True, expiry_date__gte=timezone.now()).first()
if active_user_ban:
raise forms.ValidationError("This account has been banned until {}. Reason: {}".format(active_user_ban.expiry_date, active_user_ban.reason), code='banned')
self.user = user
return cleaned_data
class User_tools_Form(forms.ModelForm):
class manage_user(forms.ModelForm):
username = forms.CharField(max_length = 64, required = True, help_text = 'Usernames are used when signing in, so be sure to let this user know if you change this.')
has_mh = forms.BooleanField(required = False, label='Use Mii', help_text='Turn this on to use Mii Hashes instead of normal profile pictures.')
avatar = forms.CharField(required = False, help_text = 'If \"Use Mii\" is turned on, The Mii Hash should reside here, otherwise any good old URL will do.')
@@ -148,7 +228,7 @@ class User_tools_Form(forms.ModelForm):
raise forms.ValidationError("The username contains invalid characters.")
return username
class Profile_tools_Form(forms.ModelForm):
class manage_profile(forms.ModelForm):
# applying classes is super weird.
comment = forms.CharField(required=False, widget=forms.Textarea(attrs={'class': 'textarea'}))
let_freedom = forms.BooleanField(required=False, label='Let this user upload images?', help_text='If you disable this, This user will not be able to post images, videos or make a new account.')
@@ -156,22 +236,24 @@ class Profile_tools_Form(forms.ModelForm):
model = Profile
fields = ['comment', 'country', 'whatareyou', 'weblink', 'external', 'let_freedom', 'limit_post', 'cannot_edit']
class Give_warning_form(forms.ModelForm):
class give_warning(forms.ModelForm):
reason = forms.CharField(required=True, widget=forms.Textarea(attrs={'class': 'textarea'}))
# Super simple, does not need to be it's own form but whatever.
class Meta:
model = Warning
fields = ['reason']
class Give_Ban_Form(forms.ModelForm):
class give_ban(forms.ModelForm):
BAN_OPTIONS = [
(1, '1 Day'),
(2, '2 Days'),
(3, '3 Days'),
(7, '1 Week'),
(14, '2 Weeks'),
(21, '3 Weeks'),
(30, '1 Month'),
(60, '2 Months'),
(90, '3 Months'),
(365, '1 Year'),
(None, 'Forever'),
]
@@ -189,10 +271,147 @@ class Give_Ban_Form(forms.ModelForm):
model = Ban
fields = ['reason', 'expiry_date']
class Give_Ban_Form_Edit(forms.ModelForm):
class edit_ban(forms.ModelForm):
reason = forms.CharField(required=True, widget=forms.Textarea(attrs={'class': 'textarea'}))
expiry_date = forms.DateTimeField(widget=forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'DateTimeInput'}, format='%Y-%m-%dT%H:%M'))
active = forms.BooleanField(required=False)
class Meta:
model = Ban
fields = ['reason', 'expiry_date', 'active']
class profile_settings_page(forms.Form):
# This form is an absolute clusterfuck.
screen_name = forms.CharField(max_length=32, required=True)
pronouns = forms.CharField(max_length=16, required=False)
profile_comment = forms.CharField(max_length=2200, required=False)
country = forms.CharField(max_length=64, required=False)
email = forms.CharField(max_length=255, required=False)
website = forms.CharField(max_length=255, required=False)
external = forms.CharField(max_length=255, required=False)
whatareyou = forms.CharField(max_length=255, required=False)
color = forms.CharField(max_length=7, required=False)
id_visibility = forms.IntegerField(max_value=2, min_value=0)
let_friendrequest = forms.IntegerField(max_value=2, min_value=0)
yeahs_visibility = forms.IntegerField(max_value=2, min_value=0)
comments_visibility = forms.IntegerField(max_value=2, min_value=0)
theme = forms.CharField(max_length=7, required=False)
reset_theme = forms.BooleanField(required=False)
'''PFP stuff'''
avatar = forms.IntegerField(max_value=3, min_value=0, required=False)
origin_id = forms.CharField(max_length=16, min_length=6, required=False)
mh = forms.CharField(max_length=16, required=False)
file= forms.ImageField(required=False)
def clean_email(self):
email = self.cleaned_data.get('email')
validator = EmailValidator()
if email:
try:
validator(email)
except ValidationError:
raise forms.ValidationError("Invalid email.")
return email
return None
def is_color_acceptable(self, hex_color):
hex_color = hex_color.lstrip('#')
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
r /= 255.0
g /= 255.0
b /= 255.0
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
# Define thresholds
too_dark_threshold = 0.2
too_bright_threshold = 0.8
if luminance < too_dark_threshold:
return False
elif luminance > too_bright_threshold:
return False
else:
return True
def get_gravatar(self, email):
try:
page = urllib.request.urlopen('https://gravatar.com/avatar/'+ md5(email.encode('utf-8').lower()).hexdigest() +'?d=404&s=128')
except:
return False
return page.geturl()
# what an abomination
def save(self, user, commit=True):
profile = user.profile()
user.nickname = self.cleaned_data['screen_name']
user.email = self.cleaned_data['email']
if not self.cleaned_data.get('origin_id'):
profile.origin_info = None
profile.origin_id = None
avatar_option = self.cleaned_data.get('avatar')
match avatar_option:
# will add case 3 later who gives a shit
case 2:
if self.cleaned_data.get('file'):
user.avatar_upload = compress_and_resize_content(image=self.cleaned_data.get('file'), icon=True)
user.avatar_type = 0
case 1:
user.avatar_input = util.get_gravatar(user.email) or None
user.avatar_type = 1
case 0:
if not self.cleaned_data.get('origin_id'):
user.avatar_type = 0
user.avatar_input = None
else:
if self.cleaned_data.get('mh'):
user.avatar_type = 2
user.avatar_input = self.cleaned_data.get('mh')
profile.origin_id = self.cleaned_data.get('origin_id')
profile.origin_info = json.dumps([self.cleaned_data.get('mh'), 'if you see this then something is wrong', self.cleaned_data['origin_id']])
# Setting the username color
color = self.cleaned_data.get('color')
if color:
try:
validate_color(color)
except ValidationError:
user.color = None
else:
if self.is_color_acceptable(color):
user.color = color
else:
user.color = None
# Setting the theme
theme = self.cleaned_data.get('theme')
if theme:
reset_theme = self.cleaned_data.get('reset_theme')
try:
validate_color(theme)
except ValidationError:
user.theme = None
else:
if reset_theme:
user.theme = None
pass
if self.is_color_acceptable(theme):
user.theme = theme
else:
user.theme = None
profile.comment = self.cleaned_data['profile_comment']
profile.pronoun_is = self.cleaned_data['pronouns']
profile.country = self.cleaned_data['country']
profile.weblink = self.cleaned_data['website']
profile.external = self.cleaned_data['external']
profile.whatareyou = self.cleaned_data['whatareyou']
profile.id_visibility = self.cleaned_data['id_visibility']
profile.let_friendrequest = self.cleaned_data['let_friendrequest']
profile.yeahs_visibility = self.cleaned_data['yeahs_visibility']
profile.comments_visibility = self.cleaned_data['comments_visibility']
if commit:
user.save()
profile.save()
@@ -0,0 +1,33 @@
# Generated by Django 3.2.2 on 2023-10-16 20:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('admin', '0003_logentry_add_action_flag_choices'),
('closedverse_main', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='userrequest',
name='user_ptr',
),
migrations.AlterModelOptions(
name='user',
options={},
),
migrations.AlterField(
model_name='user',
name='is_staff',
field=models.BooleanField(default=False, help_text="Allow this user to access the admin panel you're using right now? Don't forget to specify the permissions."),
),
migrations.DeleteModel(
name='ConversationInvite',
),
migrations.DeleteModel(
name='UserRequest',
),
]
@@ -0,0 +1,49 @@
# Generated by Django 3.2.2 on 2023-10-16 21:10
import closedverse_main.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('closedverse_main', '0002_auto_20231016_1355'),
]
operations = [
migrations.AddField(
model_name='comment',
name='file',
field=models.FileField(blank=True, max_length=1024, null=True, upload_to='comment_file/%Y/%m/%d/'),
),
migrations.AddField(
model_name='message',
name='file',
field=models.FileField(blank=True, max_length=1024, null=True, upload_to='message_file/%Y/%m/%d/'),
),
migrations.AddField(
model_name='post',
name='file',
field=models.FileField(blank=True, max_length=1024, null=True, upload_to='post_file/%Y/%m/%d/'),
),
migrations.AddField(
model_name='user',
name='avatar_input',
field=models.CharField(blank=True, help_text='Input a Mii Hash, or URL depending on what "Avatar type" is set to..', max_length=1200, null=True),
),
migrations.AddField(
model_name='user',
name='avatar_type',
field=models.SmallIntegerField(choices=[(0, 'ImageField'), (1, 'URL / Gravatar'), (2, 'Mii Hash'), (3, 'Mii Studio')], default=False, help_text='Determines the type of avatar this user has.'),
),
migrations.AddField(
model_name='user',
name='avatar_upload',
field=models.ImageField(blank=True, help_text='Note this only works if you have "Avatar type" set to "ImageField"', null=True, upload_to='avatars/'),
),
migrations.AddField(
model_name='user',
name='show_announcements',
field=models.BooleanField(default=True),
),
]
@@ -0,0 +1,60 @@
# Generated by Django 3.2.2 on 2023-10-16 21:19
from django.db import migrations
def migrate_media_to_file(apps, schema_editor):
# Migrate Comment model
Comment = apps.get_model('closedverse_main', 'Comment')
for comment in Comment.objects.all():
if comment.drawing and comment.drawing.startswith('/i/'):
comment.file = comment.drawing[3:]
comment.save()
if comment.screenshot and comment.screenshot.startswith('/i/'):
comment.file = comment.screenshot[3:]
comment.save()
# Migrate Message model
Message = apps.get_model('closedverse_main', 'Message')
for message in Message.objects.all():
if message.drawing and message.drawing.startswith('/i/'):
message.file = message.drawing[3:]
message.save()
if message.screenshot and message.screenshot.startswith('/i/'):
message.file = message.screenshot[3:]
message.save()
# Migrate Post model
Post = apps.get_model('closedverse_main', 'Post')
for post in Post.objects.all():
if post.drawing and post.drawing.startswith('/i/'):
post.file = post.drawing[3:]
post.save()
if post.screenshot and post.screenshot.startswith('/i/'):
post.file = post.screenshot[3:]
post.save()
if post.video and post.video.startswith('/i/'):
post.file = post.video[3:]
post.save()
User = apps.get_model('closedverse_main', 'User')
for user in User.objects.all():
user.avatar_input = user.avatar
if user.has_mh:
user.avatar_type = 2
else:
user.avatar_type = 1
user.save()
class Migration(migrations.Migration):
dependencies = [
('closedverse_main', '0003_auto_20231016_1410'),
]
operations = [
migrations.RunPython(migrate_media_to_file),
]
@@ -0,0 +1,113 @@
# Generated by Django 3.2.2 on 2023-10-16 22:02
import closedverse_main.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('closedverse_main', '0004_auto_20231016_1419'),
]
operations = [
migrations.RemoveField(
model_name='comment',
name='drawing',
),
migrations.RemoveField(
model_name='comment',
name='screenshot',
),
migrations.RemoveField(
model_name='message',
name='drawing',
),
migrations.RemoveField(
model_name='message',
name='screenshot',
),
migrations.RemoveField(
model_name='post',
name='drawing',
),
migrations.RemoveField(
model_name='post',
name='screenshot',
),
migrations.RemoveField(
model_name='post',
name='video',
),
migrations.RemoveField(
model_name='profile',
name='email_login',
),
migrations.RemoveField(
model_name='user',
name='avatar',
),
migrations.RemoveField(
model_name='user',
name='bg_url',
),
migrations.RemoveField(
model_name='user',
name='has_mh',
),
migrations.AlterField(
model_name='community',
name='banner',
field=models.ImageField(blank=True, null=True, upload_to='community_banners/'),
),
migrations.AlterField(
model_name='community',
name='ico',
field=models.ImageField(blank=True, null=True, upload_to='community_icons/'),
),
migrations.AlterField(
model_name='message',
name='url',
field=models.URLField(blank=True, max_length=1200, null=True),
),
migrations.AlterField(
model_name='profile',
name='comment',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='profile',
name='country',
field=models.CharField(blank=True, max_length=120),
),
migrations.AlterField(
model_name='profile',
name='external',
field=models.CharField(blank=True, max_length=255),
),
migrations.AlterField(
model_name='profile',
name='pronoun_is',
field=models.CharField(blank=True, max_length=16),
),
migrations.AlterField(
model_name='profile',
name='weblink',
field=models.CharField(blank=True, max_length=1200),
),
migrations.AlterField(
model_name='profile',
name='whatareyou',
field=models.CharField(blank=True, max_length=120),
),
migrations.AlterField(
model_name='user',
name='color',
field=closedverse_main.models.ColorField(blank=True, max_length=18, null=True),
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
]
+130 -210
View File
@@ -17,7 +17,7 @@ import uuid, json, base64
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.urls import reverse
from django.db.models.signals import pre_delete
import mimetypes
import re
import unicodedata
import random
@@ -52,14 +52,14 @@ class UserManager(BaseUserManager):
)
profile = Profile.objects.model()
if nn:
user.avatar = nn[0]
user.avatar_input = nn[0]
profile.origin_id = nn[2]
profile.origin_info = json.dumps(nn)
user.has_mh = True
user.avatar_type = 2
else:
user.avatar = util.get_gravatar(email) or ('s' if getrandbits(1) else '')
user.avatar_input = util.get_gravatar(email) or ('s' if getrandbits(1) else '')
user.has_mh = False
user.avatar_type = 1
user.set_password(password)
user.save(using=self._db)
profile.user = user
@@ -88,10 +88,6 @@ class UserManager(BaseUserManager):
user = user.first()
# If the user is an admin, say that they don't exist, actually no...
# Or, if the user doesn't want username login, don't let them if they didn't enter their email
if user.profile('email_login') == 2 and not user.email == username:
return None
elif user.profile('email_login') == 0 and user.email == username:
return None
try:
passwd = user.check_password(password)
# Check if the password is a valid bcrypt
@@ -136,15 +132,17 @@ class Role(models.Model):
# as of writing, mii-secure is unstable, nintendo please do not f*ck me for this
mii_domain = 'https://s3.us-east-1.amazonaws.com/mii-images.account.nintendo.net/'
studiomii_domain = 'https://studio.mii.nintendo.com/'
class User(AbstractBaseUser, PermissionsMixin):
id = models.AutoField(primary_key=True)
username = models.CharField(max_length=32, unique=True)
nickname = models.CharField(max_length=64, null=True)
password = models.CharField(max_length=128)
email = models.EmailField(null=True, blank=True, default='')
has_mh = models.BooleanField(default=False, help_text='Don\'t touch this. This is if the user\'s avatar is set to a Mii.')
avatar = models.CharField(max_length=1200, blank=True, default='')
theme = ColorField(blank=True, null=True)
email = models.EmailField(null=True, blank=True)
avatar_type = models.SmallIntegerField(default=False, help_text='Determines the type of avatar this user has.', choices=((0, 'ImageField'), (1, 'URL / Gravatar'), (2, 'Mii Hash'), (3, 'Mii Studio')))
avatar_input = models.CharField(max_length=1200, null=True, blank=True, help_text='Input a Mii Hash, or URL depending on what "Avatar type" is set to..')
avatar_upload = models.ImageField(blank=True, null=True, upload_to='avatars/', help_text='Note this only works if you have "Avatar type" set to "ImageField"')
# LEVEL: 0-1 is default, everything else is just levels
level = models.SmallIntegerField(default=0, help_text='This is the level of authority. People with a lower level cannot edit those with a higher level. This also grants additional permissions outside of the Django Admin Panel.')
role = models.ForeignKey(Role, blank=True, null=True, on_delete=models.SET_NULL, help_text='This will show a funny badge and text on this user\'s profile. This does not grant the user any additional power and is only visual.')
@@ -153,15 +151,16 @@ class User(AbstractBaseUser, PermissionsMixin):
# C Tokens are things that let you make communities and shit.
c_tokens = models.IntegerField(default=1, help_text='How many communities should this user be allowed to make?')
# Things that don't have to do with auth lol
# Personalization stuff
hide_online = models.BooleanField(default=False, help_text='If this is ticked, the user has opted to hide their online status.')
color = ColorField(default='', null=True, blank=True)
color = ColorField(null=True, blank=True)
theme = ColorField(blank=True, null=True)
show_announcements = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False, help_text='Allow this user to access the admin panel you\'re using right now? Don\'t forget to specify the permissions.')
is_active = models.BooleanField(default=True, help_text='If this is off, the user is basically banned and can\'t do shit here')
is_superuser = models.BooleanField(default=False, help_text='Overrides django groups. This also allows you to change people\'s perms.')
can_invite = models.BooleanField(default=True, help_text='Can this user invite new users? This does not matter unless the invite system is turned on.')
bg_url = models.CharField(max_length=300, null=True, blank=True)
is_anonymous = False
is_authenticated = True
@@ -217,11 +216,10 @@ class User(AbstractBaseUser, PermissionsMixin):
return infodecode[0]
def unread_warning(self):
return Notification.objects.filter(type=5, to=self, read=False).exists()
def has_plain_avatar(self):
if not self.has_mh and '/' in self.avatar and not 'gravatar.com' in self.avatar:
return True
def has_avatar(self):
if not self.avatar or len(self.avatar) == 1:
if self.avatar_type == 0 and not self.avatar_upload:
return False
if not self.avatar_type == 0 and not self.avatar_input:
return False
return True
def let_yeahnotifs(self):
@@ -233,10 +231,12 @@ class User(AbstractBaseUser, PermissionsMixin):
return False
today_min = timezone.datetime.combine(timezone.datetime.today(), time.min)
today_max = timezone.datetime.combine(timezone.datetime.today(), time.max)
# recent_posts =
# Posts made by the user today + posts made by the IP today +
# same thing except with comments
recent_posts = Post.real.filter(Q(creator=self.id, created__range=(today_min, today_max)) | Q(creator__addr=self.addr, created__range=(today_min, today_max))).count() + Comment.real.filter(Q(creator=self.id, created__range=(today_min, today_max)) | Q(creator__addr=self.addr, created__range=(today_min, today_max))).count()
# recent_posts
recent_posts = Post.real.filter(
Q(creator=self) & Q(created__range=(today_min, today_max))
).count() + Comment.real.filter(
Q(creator=self) & Q(created__range=(today_min, today_max))
).count()
# Posts remaining
return int(limit) - recent_posts
@@ -268,7 +268,24 @@ class User(AbstractBaseUser, PermissionsMixin):
else:
return True
def do_avatar(self, feeling=0):
if self.has_mh and self.avatar:
anon = settings.STATIC_URL + 'img/anonymous-mii.png'
match self.avatar_type:
case 3:
if self.avatar_input:
feeling = {
0: 'normal',
1: 'smile_open_mouth',
2: 'like_wink_left',
3: 'surprise_open_mouth',
4: 'frustrated',
5: 'sorrow',
}.get(feeling, "normal")
url = '{2}miis/image.png?data={0}&type=face&expression={1}&width=128'.format(self.avatar_input, feeling, studiomii_domain)
return url
else:
return anon
case 2:
if self.avatar_input:
feeling = {
0: 'normal',
1: 'happy',
@@ -277,15 +294,20 @@ class User(AbstractBaseUser, PermissionsMixin):
4: 'frustrated',
5: 'puzzled',
}.get(feeling, "normal")
url = '{2}{0}_{1}_face.png'.format(self.avatar, feeling, mii_domain)
url = '{2}{0}_{1}_face.png'.format(self.avatar_input, feeling, mii_domain)
return url
elif not self.avatar:
return settings.STATIC_URL + 'img/anonymous-mii.png'
elif self.avatar == 's':
return settings.STATIC_URL + 'img/anonymous-mii-sad.png'
else:
return self.avatar
return anon
case 1:
if self.avatar_input:
return self.avatar_input
else:
return anon
case 0:
if self.avatar_upload:
return self.avatar_upload.url
else:
return anon
def num_yeahs(self):
return self.yeah_set.count()
def num_posts(self):
@@ -519,18 +541,12 @@ class User(AbstractBaseUser, PermissionsMixin):
return self.save(update_fields=['addr', 'last_login'])
return self.save(update_fields=['last_login'])
def has_postspam(self, body, screenshot=None, drawing=None):
def has_postspam(self, body, file=None):
latest_post = self.post_set.order_by('-created')[:1]
if not latest_post:
return False
latest_post = latest_post.first()
if drawing and latest_post.drawing:
if drawing == latest_post.drawing:
return True
elif latest_post.screenshot and screenshot and not drawing:
if latest_post.screenshot == screenshot and latest_post.body == body:
return True
elif latest_post.body and body and not latest_post.screenshot and not latest_post.drawing:
if latest_post.body and body and not latest_post.file:
if latest_post.body == body:
return True
return False
@@ -654,8 +670,8 @@ class Community(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
ico = models.ImageField(null=True, blank=True)
banner = models.ImageField(null=True, blank=True)
ico = models.ImageField(null=True, blank=True, upload_to='community_icons/')
banner = models.ImageField(null=True, blank=True, upload_to='community_banners/')
# Type: 0 - general, 1 - game, 2 - special
type = models.SmallIntegerField(default=0, help_text='The category the community belongs in, setting this to None will remove the community.', choices=((0, 'General'), (1, 'Game'), (2, 'Special'), (3, 'User Community'), (4, 'Hide')))
platform = models.SmallIntegerField(default=0, choices=((0, 'None'), (1, '3DS'), (2, 'Wii U'), (3, 'Switch'), (4, '3DS and Wii U'), (5, 'PC'), (6, 'Xbox'), (7, 'Playstation')))
@@ -788,67 +804,11 @@ class Community(models.Model):
if self.has_favorite(request):
return request.user.communityfavorite_set.filter(community=self).delete()
def setup(self, request):
if request.user.is_authenticated:
self.post_perm = self.post_perm(request)
self.has_favorite = self.has_favorite(request)
def create_post(self, request):
if not self.post_perm(request):
return 4
limit = request.user.limit_remaining()
if not limit is False and not limit > 0:
return 8
del(limit)
#if Post.real.filter(Q(creator=request.user) | Q(creator__addr=request.user.addr), created__gt=timezone.now() - timedelta(seconds=10)).exists():
# return 3
if request.POST.get('url'):
try:
URLValidator()(value=request.POST['url'])
except ValidationError:
return 5
if not request.user.has_freedom() and (request.POST.get('url') or request.FILES.get('screen') or request.FILES.get('video')):
return 6
if not request.user.is_active:
return 6
if request.user.unread_warning():
return 13
if len(request.POST['body']) > 2200 or (len(request.POST['body']) < 1 and not request.POST.get('_post_type') == 'painting'):
return 1
upload = None
drawing = None
video = None
body = request.POST.get('body')
for c in body:
if unicodedata.combining(c):
return 12
if request.FILES.get('screen'):
upload = util.image_upload(request.FILES['screen'], True)
if upload == 1:
return 2
if request.FILES.get('video'):
video = util.video_upload(request.FILES['video'])
if video == 1:
return 11
if request.POST.get('_post_type') == 'painting':
if not request.POST.get('painting'):
return 2
drawing = util.image_upload(request.POST['painting'], False, True)
if drawing == 1:
return 2
# Check for spam using our OWN ALGO!!!!!!!!!
if request.user.has_postspam(body, upload, drawing):
return 7
for keyword in ['faggot', 'fag', 'nigger', 'nigga']:
if keyword in body.lower():
return 9
if body.isspace() and not drawing:
return 10
new_post = self.post_set.create(body=body, creator=request.user, community=self, feeling=int(request.POST.get('feeling_id', 0)), spoils=bool(request.POST.get('is_spoiler')), screenshot=upload, drawing=drawing, url=request.POST.get('url'), video=video)
new_post.is_mine = True
return new_post
def search(query='', limit=50, offset=0, request=None):
return Community.objects.filter(Q(name__icontains=query) | Q(description__contains=query)).exclude(type=4).order_by('-created')[offset:offset + limit]
@@ -872,9 +832,7 @@ class Post(models.Model):
community = models.ForeignKey(Community, null=True, on_delete=models.CASCADE)
feeling = models.SmallIntegerField(default=0, choices=feelings)
body = models.TextField(null=True)
drawing = models.CharField(max_length=200, null=True, blank=True)
screenshot = models.CharField(max_length=1200, null=True, blank=True, default='')
video = models.CharField(max_length=256, null=True, blank=True, default='')
file = models.FileField(max_length=1024, null=True, blank=True, upload_to='post_file/%Y/%m/%d/')
url = models.URLField(max_length=1200, null=True, blank=True, default='')
spoils = models.BooleanField(default=False)
disable_yeah = models.BooleanField(default=False)
@@ -898,8 +856,6 @@ class Post(models.Model):
def trun(self):
if self.is_rm:
return 'deleted'
if self.drawing:
return 'drawing'
else:
return self.body
def yt_vid(self):
@@ -917,6 +873,26 @@ class Post(models.Model):
return (self.creator == user)
else:
return False
def file_type(self):
mimetypes.add_type("image/webp", ".webp")
# Get the file MIME type
if not self.file:
return None
mime_type, encoding = mimetypes.guess_type(self.file.name)
if mime_type:
# Determine the type of file
type_main, type_sub = mime_type.split('/')
if type_main == 'image':
return 1
elif type_main == 'video':
return 2
elif type_main == 'audio':
return 3
else:
return 0
else:
return 0
#def yeah_notification(self, request):
# ???? What is this
#Notification.give_notification
@@ -1027,43 +1003,6 @@ class Post(models.Model):
for post in comments:
post.setup(request)
return comments
def create_comment(self, request):
if not self.can_comment(request):
return False
limit = request.user.limit_remaining()
if not limit is False and not limit > 0:
return 8
del(limit)
if self.is_mine(request.user) and Comment.real.filter(creator=request.user, created__gt=timezone.now() - timedelta(seconds=2)).exists():
return 3
elif not self.is_mine(request.user) and Comment.real.filter(creator=request.user, created__gt=timezone.now() - timedelta(seconds=10)).exists():
return 3
if not request.user.has_freedom() and (request.POST.get('url') or request.FILES.get('screen')):
return 6
for c in request.POST['body']:
if unicodedata.combining(c):
return 12
if not request.user.is_active:
return 6
if len(request.POST['body']) > 2200 or (len(request.POST['body']) < 1 and not request.POST.get('_post_type') == 'painting'):
return 1
if request.user.unread_warning():
return 13
upload = None
drawing = None
if request.FILES.get('screen'):
upload = util.image_upload(request.FILES['screen'], True)
if upload == 1:
return 2
if request.POST.get('_post_type') == 'painting':
if not request.POST.get('painting'):
return 2
drawing = util.image_upload(request.POST['painting'], False, True)
if drawing == 1:
return 2
new_post = self.comment_set.create(body=request.POST.get('body'), creator=request.user, community=self.community, original_post=self, feeling=int(request.POST.get('feeling_id', 0)), spoils=bool(request.POST.get('is_spoiler')), drawing=drawing, screenshot=upload)
new_post.is_mine = True
return new_post
def recent_comment(self):
if self.number_comments() < 1:
return False
@@ -1118,12 +1057,6 @@ class Post(models.Model):
else:
self.status = 2
AuditLog.objects.create(type=0, post=self, user=self.creator, by=request.user)
if self.screenshot:
util.image_rm(self.screenshot)
self.screenshot = None
if self.drawing:
util.image_rm(self.drawing)
self.drawing = None
self.save()
def setup(self, request):
self.has_yeah = self.has_yeah(request)
@@ -1143,8 +1076,7 @@ class Comment(models.Model):
community = models.ForeignKey(Community, on_delete=models.CASCADE)
feeling = models.SmallIntegerField(default=0, choices=feelings)
body = models.TextField(null=True)
screenshot = models.CharField(max_length=1200, null=True, blank=True, default='')
drawing = models.CharField(max_length=200, null=True, blank=True)
file = models.FileField(max_length=1024, null=True, blank=True, upload_to='comment_file/%Y/%m/%d/')
spoils = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
edited = models.DateTimeField(auto_now=True)
@@ -1164,10 +1096,27 @@ class Comment(models.Model):
def trun(self):
if self.is_rm:
return 'deleted'
if self.drawing:
return '(drawing)'
else:
return self.body
def file_type(self):
mimetypes.add_type("image/webp", ".webp")
# Get the file MIME type
if not self.file:
return None
mime_type, encoding = mimetypes.guess_type(self.file.name)
if mime_type:
# Determine the type of file
type_main, type_sub = mime_type.split('/')
if type_main == 'image':
return 1
elif type_main == 'video':
return 2
elif type_main == 'audio':
return 3
else:
return 0
else:
return 0
def is_mine(self, user):
if user.is_authenticated:
return (self.creator == user)
@@ -1238,12 +1187,6 @@ class Comment(models.Model):
else:
self.status = 2
AuditLog.objects.create(type=1, comment=self, user=self.creator, by=request.user)
if self.screenshot:
util.image_rm(self.screenshot)
self.screenshot = None
if self.drawing:
util.image_rm(self.drawing)
self.drawing = None
self.save()
def setup(self, request):
self.has_yeah = self.has_yeah(request)
@@ -1277,23 +1220,19 @@ class Profile(models.Model):
origin_id = models.CharField(max_length=16, null=True, blank=True)
origin_info = models.CharField(max_length=255, null=True, blank=True)
comment = models.TextField(blank=True, default='')
country = models.CharField(max_length=120, blank=True, default='')
whatareyou = models.CharField(max_length=120, blank=True, default='')
comment = models.TextField(blank=True, null=True)
country = models.CharField(max_length=120, blank=True)
whatareyou = models.CharField(max_length=120, blank=True)
#birthday = models.DateField(null=True, blank=True)
id_visibility = models.SmallIntegerField(default=0, choices=visibility)
pronoun_is = models.IntegerField(default=0, help_text='haha attack helicopter.', choices=(
(0, "Not specified"), (1, "He/him"), (2, "She/her"), (3, "He/she"), (4, "It"), (5, "They/Them")
))
pronoun_is = models.CharField(max_length=16, blank=True)
let_friendrequest = models.SmallIntegerField(default=0, choices=visibility)
yeahs_visibility = models.SmallIntegerField(default=0, choices=visibility)
comments_visibility = models.SmallIntegerField(default=2, choices=visibility)
#relationship_visibility = models.SmallIntegerField(default=0, choices=visibility)
weblink = models.CharField(max_length=1200, blank=True, default='')
#gameskill = models.SmallIntegerField(default=0)
external = models.CharField(max_length=255, blank=True, default='')
weblink = models.CharField(max_length=1200, blank=True)
external = models.CharField(max_length=255, blank=True)
favorite = models.ForeignKey(Post, blank=True, null=True, on_delete=models.SET_NULL)
let_yeahnotifs = models.BooleanField(default=True)
@@ -1302,9 +1241,7 @@ class Profile(models.Model):
#let_avatar = models.BooleanField(default=False)
# Post limit, 0 for none
limit_post = models.SmallIntegerField(default=0, help_text='Great for spammers, set to \"0\" to remove the restriction.')
# If this is true, the user can't change their avatar or nickname
cannot_edit = models.BooleanField(default=False, help_text='Make it so this user cannot change settings.')
email_login = models.SmallIntegerField(default=1, choices=((0, 'Do not allow'), (1, 'Okay'), (2, 'Only allow')))
def __str__(self):
return "profile for " + self.user.username
@@ -1609,38 +1546,13 @@ class Conversation(models.Model):
for msg in msgs:
msg.mine = msg.mine(request.user)
return msgs
def make_message(self, request):
if not request.user.has_freedom() and (request.POST.get('url') or request.FILES.get('screen')):
return 6
if Message.real.filter(creator=request.user, created__gt=timezone.now() - timedelta(seconds=2)).exists():
return 3
if len(request.POST['body']) > 50000 or (len(request.POST['body']) < 1 and not request.POST.get('_post_type') == 'painting'):
return 1
if request.user.unread_warning():
return 4
upload = None
drawing = None
if request.FILES.get('screen'):
upload = util.image_upload(request.FILES['screen'], True)
if upload == 1:
return 2
if request.POST.get('_post_type') == 'painting':
if not request.POST.get('painting'):
return 2
drawing = util.image_upload(request.POST['painting'], False, True)
if drawing == 1:
return 2
new_post = self.message_set.create(body=request.POST.get('body'), creator=request.user, feeling=int(request.POST.get('feeling_id', 0)), drawing=drawing, screenshot=upload)
new_post.mine = True
return new_post
class Message(models.Model):
id = models.AutoField(primary_key=True)
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE)
feeling = models.SmallIntegerField(default=0, choices=feelings)
body = models.TextField(null=True)
drawing = models.CharField(max_length=200, null=True, blank=True)
screenshot = models.CharField(max_length=1200, null=True, blank=True, default='')
url = models.URLField(max_length=1200, null=True, blank=True, default='')
file = models.FileField(max_length=1024, null=True, blank=True, upload_to='message_file/%Y/%m/%d/')
url = models.URLField(max_length=1200, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
read = models.BooleanField(default=False)
is_rm = models.BooleanField(default=False)
@@ -1654,10 +1566,27 @@ class Message(models.Model):
def trun(self):
if self.is_rm:
return 'deleted'
if self.drawing:
return '(drawing)'
else:
return self.body
def file_type(self):
mimetypes.add_type("image/webp", ".webp")
# Get the file MIME type
if not self.file:
return None
mime_type, encoding = mimetypes.guess_type(self.file.name)
if mime_type:
# Determine the type of file
type_main, type_sub = mime_type.split('/')
if type_main == 'image':
return 1
elif type_main == 'video':
return 2
elif type_main == 'audio':
return 3
else:
return 0
else:
return 0
def mine(self, user):
if self.creator == user:
return True
@@ -1665,14 +1594,7 @@ class Message(models.Model):
def rm(self, request):
if self.conversation.source == request.user or self.conversation.target == request.user:
self.is_rm = True
if self.screenshot:
util.image_rm(self.screenshot)
self.screenshot = None
if self.drawing:
util.image_rm(self.drawing)
self.drawing = None
self.save()
def makeopt(ls):
if len(ls) < 1:
raise ValueError
@@ -1843,12 +1765,10 @@ class ProfileHistory(models.Model):
# blah blah blah
# this method will be executed when...
def rm_post_image(sender, instance, **kwargs):
if instance.screenshot:
util.image_rm(instance.screenshot)
if instance.drawing:
util.image_rm(instance.drawing)
'''def rm_post_image(sender, instance, **kwargs):
if instance.file:
util.image_rm(instance.file)
# when pre_delete happens on these
pre_delete.connect(rm_post_image, sender=Post)
pre_delete.connect(rm_post_image, sender=Comment)
pre_delete.connect(rm_post_image, sender=Message)
pre_delete.connect(rm_post_image, sender=Message)'''
@@ -60,13 +60,13 @@
</div>
{% endif %}
<div id="the-post">
{% if comment.drawing %}
<p class="reply-content-memo"><img src="{{ comment.drawing }}" class="post-memo"></p>
{% else %}
<p class="reply-content-text">{{ comment.body|linebreaksbr }}</p>
{% if comment.file %}
{% if comment.file_type == 1 %}<div class="screenshot-container still-image"><img src="{{ comment.file.url }}"></div>
{% elif comment.file_type == 2 %}<div class="screenshot-container video"><video controls src="{{ comment.file.url }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif comment.file_type == 3 %}<div class="screenshot-container audio"><audio controls src="{{ comment.file.url }}"></audio></div>
{% elif comment.file_type == 0 %}<a class="button download-confirm" href="{{ comment.file.url }}">Download {{ comment.file }}</a>
{% endif %}
{% if comment.screenshot %}
<div class="screenshot-container still-image"><img src="{{ comment.screenshot }}"></div>
{% endif %}
<div class="post-meta">
<button type="button"{% if not comment.can_yeah %} disabled{% endif %} class="symbol submit yeah-button
@@ -11,14 +11,6 @@
{% feeling_selector %}
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text">
<input type="radio" name="_post_type" value="body">
</label></li>
<li><label class="textarea-menu-memo">
<input type="radio" name="_post_type" value="painting">
</label></li>
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea" maxlength="2200" placeholder="Add a comment here." data-required></textarea>
</div>
@@ -21,27 +21,23 @@
<span class="spoiler">Edited</span>·
{% endif %}
<a class="timestamp" {% if post.spoils and not post.is_mine %}data-href-hidden{% else %}href{% endif %}="{% if post.is_reply %}{% url "main:comment-view" post.id %}{% else %}{% url "main:post-view" post.id %}{% endif %}">{% time post.created %}</a>
{% if post.drawing %}
<span class="spoiler">(handwritten)</span>
{% endif %}
</p>
<div class="body post-content">
{% if post.yt_vid %}
{% if post.yt_vid and not post.file_type == 2 %}
<a href="{% url "main:post-view" post.id %}" class="screenshot-container video"><img height="48" src="https://i.ytimg.com/vi/{{ post.yt_vid }}/default.jpg"></a>
{% elif post.discord_vid %}
<div class="screenshot-container video"><video src="{{ post.url }}" height="48"></video></div>
{% endif %}
{% if post.drawing %}
<p class="post-content-memo"><img src="{{ post.drawing }}" class="post-memo"></p>
{% else %}
{% if post.has_line_trun %}
<p class="post-content-text">{{ post.body|truncatechars:200 }}</p>
{% else %}
<p class="post-content-text">{{ post.body|truncatechars:200|linebreaksbr }}</p>
{% endif %}
{% if post.file %}
{% if post.file_type == 1 %}<a class="screenshot-container still-image" href="{% if post.is_reply %}{% url "main:comment-view" post.id %}{% else %}{% url "main:post-view" post.id %}{% endif %}"><img src="{{ post.file.url }}"></a>
{% elif post.file_type == 2 %}<div class="screenshot-container still-image"><video controls src="{{ post.file.url }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif post.file_type == 3 %}<div class="screenshot-container audio"><audio controls src="{{ post.file.url }}"></audio></div>
{% else %}<a>File attached</a>
{% endif %}
{% endif %}
{% if post.screenshot %}<a class="screenshot-container still-image" href="{% if post.is_reply %}{% url "main:comment-view" post.id %}{% else %}{% url "main:post-view" post.id %}{% endif %}"><img src="{{ post.screenshot }}"></a>{% endif %}
{% if post.video %}<div class="screenshot-container still-image"><video controls src="{{ post.video }}" style="max-width:100%;max-height: 450px;"></video></div>{% endif %}
{% if post.spoils and not post.is_mine and not post.creator.banned %}
<div class="hidden-content"><p>This post contains spoilers.</p>
<button type="button" class="hidden-content-button">View Post</button>
@@ -1,13 +1,7 @@
<!-- <div class="file-button-container">
<span class="input-div">Image <span>PNG, JPG, and GIF are allowed.</span></span>
<input type="file" class="file-button" accept="image/png, image/jpeg, image/jpg, image/svg+xml, image/bmp, image/gif">
</div>
-->
<label class="file-button-container">
<span class="input-label">Image <span id="image-dimensions">PNG and JPEG are allowed.</span></span>
<span class="input-label">File <span id="image-dimensions">Images, videos and audio are supported.</span></span>
<span class="button file-upload-button">Upload</span>
<input accept="image/gif, image/png, image/jpeg, image/jpg, image/svg+xml, image/webp, image/bmp" type="file" style="display: none;" id="upload-file" name="screen">
<input type="file" style="display: none;" id="upload-file" name="file">
<div id="upload-preview-container" class="screenshot-container still-image" style="display: none;">
<img id="upload-preview">
<!-- not functional currently
@@ -15,8 +9,4 @@
-->
</div>
</label>
<div class="file-button-container">
<span class="input-label">Video <span>MP4s and WEBMs are allowed.</span></span>
<input type="file" class="file-button" name="video" accept="video/mp4, video/webm">
</div>
@@ -3,14 +3,6 @@
{% csrf_token %}
{% feeling_selector %}
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text">
<input type="radio" name="_post_type" value="body">
</label></li>
<li><label class="textarea-menu-memo">
<input type="radio" name="_post_type" value="painting">
</label></li>
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea " maxlength="12000" placeholder="Write a message here." data-open-folded-form="" data-required=""></textarea>
</div>
@@ -10,8 +10,12 @@
{% else %}
<div class="post-content-text">{{ message.body }}</div>
{% endif %}
{% if message.screenshot %}
<div class="screenshot-container still-image"><img src="{{ message.screenshot }}"></div>
{% if message.file %}
{% if message.file_type == 1 %}<div class="screenshot-container still-image"><img src="{{ message.file.url }}"></div>
{% elif message.file_type == 2 %}<div class="screenshot-container"><video controls src="{{ message.file.url }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif message.file_type == 3 %}<div class="screenshot-container"><audio controls src="{{ message.file.url }}"></audio></div>
{% elif message.file_type == 0 %}<a class="button download-confirm" href="{{ message.file.url }}">Download {{ message.file }}</a>
{% endif %}
{% endif %}
</div>
</div>
@@ -5,9 +5,6 @@
<p class="user-name"><a {% if comment.creator.color %}style=color:{{ comment.creator.color }}{% endif %} href="{% url "main:user-view" comment.creator.username %}">{{ comment.creator.nickname }}</a></p>
<p class="timestamp-container">
<a class="timestamp" {% if comment.spoils and not comment.is_mine %}data-href-hidden{% else %}href{% endif %}="{% url "main:comment-view" comment.id %}">{% time comment.created %}</a>
{% if comment.drawing %}
<span class="spoiler">(handwritten)</span>
{% endif %}
{% if comment.has_edit %}
<span class="spoiler">· Edited</span>
{% endif %}
@@ -15,13 +12,13 @@
</p>
</div>
{% if comment.drawing %}
<p class="reply-content-memo"><img src="{{ comment.drawing }}"></p>
{% else %}
<div class="reply-content-text">{{ comment.body|linebreaksbr }}</div>
{% if comment.file %}
{% if comment.file_type == 1 %}<div class="screenshot-container still-image"><img src="{{ comment.file.url }}"></div>
{% elif comment.file_type == 2 %}<div class="screenshot-container"><video controls src="{{ comment.file.url }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif comment.file_type == 3 %}<div class="screenshot-container"><audio controls src="{{ comment.file.url }}"></audio></div>
{% else %}<a>File attached</a>
{% endif %}
{% if comment.screenshot %}
<div class="screenshot-container still-image"><img src="{{ comment.screenshot }}"></div>
{% endif %}
{% if comment.spoils and not comment.is_mine and not comment.creator.banned %}
<div class="hidden-content"><p>This comment contains spoilers.</p>
@@ -10,19 +10,6 @@
{% feeling_selector %}
<div class="textarea-with-menu active-text">
<menu class="textarea-menu">
<li><label class="textarea-menu-text">
<input type="radio" name="_post_type" value="body">
</label></li>
<li><label class="textarea-menu-memo">
<input type="radio" name="_post_type" value="painting">
</label></li>
<!--
<li><label class="textarea-menu-poll">
<input type="radio" name="_post_type" value="poll">
</label></li>
-->
</menu>
<div class="textarea-container">
<textarea name="body" class="textarea-text textarea " maxlength="2200" placeholder="{% if community.is_activity %}Share a post to your followers.{% else %}Share your thoughts in a post to {{ community.name }}{% endif %}" data-open-folded-form="" data-required=""></textarea>
</div>
@@ -1,4 +1,4 @@
{% load closedverse_tags %}<div {% if post.spoils and not post.is_mine or post.creator.banned %}data-href-hidden{% else %}data-href{% endif %}="{% url "main:post-view" post.id %}" class="post trigger{% if post.screenshot and not for_announcements %} with-image{% endif %}{% if post.spoils and not post.is_mine or post.creator.banned %} hidden test-hidden{% endif %}" tabindex="0">
{% load closedverse_tags %}<div {% if post.spoils and not post.is_mine or post.creator.banned %}data-href-hidden{% else %}data-href{% endif %}="{% url "main:post-view" post.id %}" class="post trigger{% if post.file_type == 1 and not for_announcements %} with-image{% endif %}{% if post.spoils and not post.is_mine or post.creator.banned %} hidden test-hidden{% endif %}" tabindex="0">
<p class="community-container"><a {% if post.community.clickable %}href="{% url "main:community-view" post.community_id %}"{% endif %}><img src="{{ post.community.icon }}" class="community-icon">{{ post.community.name }}</a></p>
@@ -16,28 +16,23 @@
<span class="spoiler">Edited ({% time post.edited %})</span>·
{% endif %}
<a class="timestamp" {% if post.spoils and not post.is_mine %}data-href-hidden{% else %}href{% endif %}="{% url "main:post-view" post.id %}">{% time post.created %}</a>
{% if post.drawing %}
<span class="spoiler">(handwritten)</span>
{% endif %}
</p>
{% if post.yt_vid %}
{% if post.yt_vid and not post.file_type == 2 %}
<a href="{% url "main:post-view" post.id %}" class="{% if not for_announcements %}screenshot-container {% else %}announcement-container {% endif %}video"><img height="48" src="https://i.ytimg.com/vi/{{ post.yt_vid }}/default.jpg"></a>
{% endif %}
{% if post.screenshot %}
<a href="{% url "main:post-view" post.id %}" class="{% if not for_announcements %}screenshot-container {% else %}announcement-container {% endif %}still-image"><img src="{{ post.screenshot }}"></a>
{% if post.file %}
{% if post.file_type == 1 %}
<a href="{% url "main:post-view" post.id %}" class="{% if not for_announcements %}screenshot-container {% else %}announcement-container {% endif %}still-image"><img src="{{ post.file.url }}"></a>
{% elif post.file_type == 2 %}
<div class="{% if not for_announcements %}screenshot-container {% else %}announcement-container {% endif %} video"><video src="{{ post.file.url }}" width="50" height="48"></video></div>
{% else %}<a>File attached</a>
{% endif %}
{% if post.video %}
<div class="{% if not for_announcements %}screenshot-container {% else %}announcement-container {% endif %} video"><video src="{{ post.video }}" width="50" height="48"></video></div>
{% endif %}
{% if post.drawing %}
<p class="post-content-memo"><img src="{{ post.drawing }}" class="post-memo"></p>
{% else %}
{% if post.has_line_trun %}
<p class="post-content-text">{{ post.body|truncatechars:100 }}</p>
{% else %}
<p class="post-content-text">{{ post.body|truncatechars:100|linebreaksbr }}</p>
{% endif %}
{% endif %}
{% if post.spoils and not post.is_mine and not post.creator.banned %}
<div class="hidden-content"><p>This post contains spoilers.
<button type="button" class="hidden-content-button">View Post</button>
@@ -23,9 +23,9 @@
{% endif %}
<div class="sidebar-container">
{% if profile.favorite.screenshot %}
{% if profile.favorite.file %}
<a href="{% url "main:post-view" profile.favorite_id %}" id="sidebar-cover">
<img src="{{ profile.favorite.screenshot }}" class="sidebar-cover-image">
<img src="{{ profile.favorite.file.url }}" class="sidebar-cover-image">
</a>
{% endif %}
{% user_sidebar_info user profile %}
@@ -150,12 +150,14 @@
</div>
{% endif %}
<div class="user-data">
{% if profile.country %}
<div class="data-content">
<h4><span>Region</span></h4>
<div class="note">
<span>{% if profile.country %}{{ profile.country }}{% else %}Not Set{% endif %}</span>
<span>{{ profile.country }}</span>
</div>
</div>
{% endif %}
<div class="data-content">
<h4><span>NNID</span></h4>
<div class="note">
@@ -192,9 +194,9 @@
{% endif %}
{% if profile.pronoun_is %}
<div class="data-content">
<h4><span>Preferred Pronoun</span></h4>
<h4><span>Pronouns</span></h4>
<div class="note">
<span>{{ profile.get_pronoun_is_display }}</span>
<span>{{ profile.pronoun_is }}</span>
</div>
</div>
{% endif %}
@@ -214,12 +216,14 @@
</div>
</div>
{% endif %}
{% if profile.weblink %}
<div class="favorite-game-genre">
<h4><span>Web URL</span></h4>
<div class="note">
<span>{% if profile.weblink %}<a class="link-confirm" href="{% if not profile.got_fullurl %}http://{% endif %}{{ profile.weblink }}">{{ profile.weblink }}</a>{% else %}Not Set{% endif %}</span>
<span><a class="link-confirm" href="{% if not profile.got_fullurl %}http://{% endif %}{{ profile.weblink }}">{{ profile.weblink }}</a></span>
</div>
</div>
{% endif %}
</div>
</div>
{% if user.community_favorites %}
@@ -32,7 +32,7 @@
</p>
{% if friend.get_latest_msg %}
<span class="timestamp">{% time friend.get_latest_msg.created %}</span>
<p class="text {% if friend.get_latest_msg.drawing or friend.get_latest_msg.is_rm %}type-memo {% endif %}{% if friend.get_latest_msg.mine %}my{% else %}other{% endif %}">{{ friend.get_latest_msg.trun }}</p>
<p class="text {% if friend.get_latest_msg.is_rm %}type-memo {% endif %}{% if friend.get_latest_msg.mine %}my{% else %}other{% endif %}">{{ friend.get_latest_msg.trun }}</p>
{% else %}
<p class="text placeholder">You haven't exchanged messages with this user yet.</p>
{% endif %}
@@ -16,7 +16,7 @@
{% if post.is_mine and not post.has_edit %}
<button type="button" class="symbol button edit-button edit-post-button"><span class="symbol-label">Edit</span></button>
{% endif %}
{% if post.is_mine and post.screenshot %}
{% if post.is_mine and post.file_type == 1 %}
<button type="button" class="symbol button edit-button profile-post-button{% if post.is_favorite %} done {% endif %}" data-action="{% if post.is_favorite %}{% url "main:post-unset-profile" post.id %}{% else %}{% url "main:post-set-profile" post.id %}{% endif %}"><span class="symbol-label">Set as profile post</span></button>
{% endif %}
{% if post.can_lock_comments %}
@@ -33,9 +33,6 @@
{% p_username post.creator %}
<p class="timestamp-container">
<span class="timestamp">{% time post.created %}</span>
{% if post.drawing %}
<span class="spoiler">(drawing)</span>
{% endif %}
<span class="spoiler-status{% if post.spoils %} spoiler{% endif %}">·Spoilers</span>
{% if post.has_edit %}
· <span class="spoiler">Edited ({% time post.edited %})</span>
@@ -62,30 +59,17 @@
</div>
{% endif %}
<div id="the-post">
{% if post.drawing %}
<p class="post-content-memo"><img src="{{ post.drawing }}" class="post-memo"></p>
{% else %}
<p class="post-content-text">{{ post.body }}</p>
{% if post.file %}
{% if post.file_type == 1 %}<div class="screenshot-container still-image"><img src="{{ post.file.url }}"></div>
{% elif post.file_type == 2 %}<div class="screenshot-container video"><video controls src="{{ post.file.url }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif post.file_type == 3 %}<div class="screenshot-container audio"><audio controls src="{{ post.file.url }}"></audio></div>
{% elif post.file_type == 0 %}<a class="button download-confirm" href="{{ post.file.url }}">Download {{ post.file }}</a>
{% endif %}
{% endif %}
{% if post.poll %}
<div class="post-poll{% if post.poll.has_vote %} selected{% endif %}" data-action="{% url "main:poll-vote" post.poll.id %}" data-action-unvote="{% url "main:poll-unvote" post.poll.id %}">
<a class="poll-votes">{{ post.poll.num_votes }} vote{% if not post.poll.num_votes == 1 %}s{% endif %}</a>
<div class="poll-options{% if post.screenshot %} with-background" style="background-image:url('{{ post.screenshot }}')"
<div class="poll-background" style="width:50%"></div>{% else %}">{% endif %}
{% for choice in post.poll.choices %}
<a class="poll-option{% if post.poll.has_vote and post.poll.has_vote.1 == choice %} selected{% endif %}" votes="{{ choice.num_votes }}">
{{ choice }}<span class="percentage">%</span>
</a>
{% endfor %}
</div>
</div>
{% elif post.screenshot %}<div class="screenshot-container still-image"><img src="{{ post.screenshot }}"></div>{% endif %}
{% if post.yt_vid %}
<div class="screenshot-container video"><iframe class="youtube-player" type="text/html" style="max-width:100%;max-height: 450px;" src="https://www.youtube.com/embed/{{ post.yt_vid }}?rel=0&modestbranding=1&iv_load_policy=3" frameborder="0"></iframe></div>
{% elif post.video %}
<div class="screenshot-container video"><video controls src="{{ post.video }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif post.discord_vid %}
<div class="DiscordCDN-container video">
<video class="discord-player" src="{{ post.url }}" style="max-width:100%;max-height: 450px;" controls></video>
@@ -7,11 +7,6 @@
<form class="setting-form" method="post" action="{% url "main:user-view" user.username %}">
<ul class="settings-list">
{% if profile.cannot_edit %}
<p>You cannot change user settings for this account</p>
{% endif %}
{% if not profile.cannot_edit %}
<li class="setting-nickname">
<p class="settings-label">Nickname</p>
<div class="center center-input">
@@ -25,20 +20,13 @@
<p class="note">Anything you write here will appear on your profile. Remember to keep it brief. Please don't write anything that'll violate <a href="{% url "main:help-rules" %}">{{ brand_name }}'s rules</a>.</p>
</li>
<!--
<li>
<p class="settings-label"><label for="relationship_visibility">Who should be able to see your friends, followers and following lists?</label></p>
<div class="select-content">
<div class="select-button">
<select name="relationship_visibility" id="relationship_visibility">
<option value="0"{% if profile.relationship_visibility == 0 %} selected{% endif %}>Everyone</option>
<option value="1"{% if profile.relationship_visibility == 1 %} selected{% endif %}>My friends</option>
<option value="2"{% if profile.relationship_visibility == 2 %} selected{% endif %}>Only me</option>
</select>
</div>
<li class="setting-pronoun">
<p class="settings-label">What are your pronouns?</p>
<div class="center center-input">
<input type="text" name="pronouns" maxlength="16" placeholder="Pronouns" value="{{ profile.pronoun_is }}">
</div>
<p class="note">Enter your pronouns here. </p>
</li>
-->
<li class="setting-country">
<p class="settings-label">Region</p>
<div class="center center-input">
@@ -55,18 +43,6 @@
<input type="text" name="email" maxlength="255" placeholder="Email address" value="{{ user.email|default_if_none:"" }}">
</div>
<p class="note">Please note that your email can be a fake one, however if you need to reset your password, this must be accessible. You can't share emails.</p>
{% if user.email %}
<p class="settings-label"><label for="email_login">Allow logging in to your account with your e-mail address?</label></p>
<div class="select-content">
<div class="select-button">
<select name="email_login" id="email_login">
<option value="0"{% if profile.email_login == 0 %} selected{% endif %}>Do not allow (username login only)</option>
<option value="1"{% if profile.email_login == 1 %} selected{% endif %}>Allow</option>
<option value="2"{% if profile.email_login == 2 %} selected{% endif %}>Only allow (email login only)</option>
</select>
</div>
</div>
{% endif %}
</li>
<li class="setting-website">
<p class="settings-label">Web URL</p>
@@ -98,9 +74,8 @@
<p class="note">This is the color your nickname will appear as. Set it to white (#ffffff) and it will return to the default. It will appear like so.</p>
{% user_sidebar_info user %}
</li>
{% if profile.origin_id %}
<li>
<p class="settings-label"><label for="id_visibility">Who should be able to see your Nintendo Network ID? ({{ profile.origin_id }})</label></p>
<p class="settings-label"><label for="id_visibility">{% if profile.origin_id %}Who should be able to see your Nintendo Network ID? ({{ profile.origin_id }}){% else %}You have not entered a Nintendo Network ID yet, If you do, who should be able to see it?{% endif %}</label></p>
<div class="select-content">
<div class="select-button">
<select name="id_visibility" id="id_visibility">
@@ -111,7 +86,6 @@
</div>
</div>
</li>
{% endif %}
<li>
<p class="settings-label"><label for="let_friendrequest">Who should be able to send you friend requests?</label></p>
<div class="select-content">
@@ -124,21 +98,6 @@
</div>
</div>
</li>
<li>
<p class="settings-label"><label for="pronoun_dot_is">What's your preferred pronoun?</label></p>
<div class="select-content">
<div class="select-button">
<select name="pronoun_dot_is" id="pronoun_dot_is">
<option value="0"{% if profile.pronoun_is == 0 %} selected{% endif %}>I don't know</option>
<option value="1"{% if profile.pronoun_is == 1 %} selected{% endif %}>He/him</option>
<option value="2"{% if profile.pronoun_is == 2 %} selected{% endif %}>She/her</option>
<option value="3"{% if profile.pronoun_is == 3 %} selected{% endif %}>He/she</option>
<option value="4"{% if profile.pronoun_is == 4 %} selected{% endif %}>It</option>
<option value="5"{% if profile.pronoun_is == 5 %} selected{% endif %}>They/Them</option>
</select>
</div>
</div>
</li>
<li>
<p class="settings-label"><label for="yeahs_visibility">Who should be able to see your Yeahs given?</label></p>
<div class="select-content">
@@ -165,16 +124,12 @@
</li>
<li class="setting-background">
<p class="settings-label">Website theme:</p>
<div class="center center-input">
<input type="text" name="bg_url" maxlength="300" placeholder="Background URL" value="{% if user.bg_url %}{{ user.bg_url }}{% endif %}">
</div>
<p class="note">To set an image as the background for this site, paste its URL into the box above. Note: this change will only be visible to you.</p>
<div class="center center-input">
<input type="hidden" name="theme" maxlength="7" placeholder="Enter a hex color value here" value="{{ user.theme }}">
<button class="button color-thing2">Open color picker</button>
</div>
<p class="current-theme" style="color:{{ user.theme }}">Remember to save and refresh the page.</p>
{% if user.theme %}<input type="checkbox" name="reset-theme">Reset to default{% endif %}
{% if user.theme %}<input type="checkbox" name="reset_theme">Reset to default{% endif %}
{% if settings.site_wide_theme_hex %}<p class="note">Default theme: "{{ settings.site_wide_theme_hex }}"</p>{% endif %}
</li>
<li class="setting-background">
@@ -195,18 +150,18 @@
</li>
<li class="setting-avatar">
<div class="icon-container">
<img class="icon nnid-icon mii{% if not user.has_mh %} none{% endif %}" src="{% miionly user.mh %}">
<img class="icon nnid-icon gravatar{% if user.has_mh or user.has_plain_avatar %} none{% endif %}" src="{{ user.gravatar }}">
<img class="icon nnid-icon mii{% if not user.avatar_type == 2 %} none{% endif %}" src="{% miionly user.mh %}">
<img class="icon nnid-icon gravatar{% if not user.avatar_type == 1 %} none{% endif %}" src="{{ user.gravatar }}">
{% if user.has_freedom %}
<img class="icon nnid-icon custom{% if not user.has_plain_avatar %} none{% endif %}" src="{{ user.do_avatar }}">
<img class="icon nnid-icon custom{% if not user.avatar_type == 0 %} none{% endif %}" src="{{ user.do_avatar }}">
{% endif %}
</div>
<p class="settings-label">Do you want the avatar shown beside your content to use the Mii from your Nintendo Network ID or a Gravatar or a custom avatar the way God intended?</p>
<label><input type="radio" name="avatar" value="0"{% if user.has_mh %} checked{% endif %}>Mii</label>
<label><input type="radio" name="avatar" value="1"{% if not user.has_mh %} checked{% endif %}>Gravatar</label>
<label><input type="radio" name="avatar" value="0"{% if user.avatar_type == 2 %} checked{% endif %}>Mii</label>
<label><input type="radio" name="avatar" value="1">Use Gravatar</label>
{% if user.has_freedom %}
<label><input type="radio" name="avatar" value="2"{% if user.has_plain_avatar%} checked{% endif %}>Custom</label>
<span id="upload-thing"{% if not user.has_plain_avatar %} class="none"{% endif %}>{% file_button %}</span>
<label><input type="radio" name="avatar" value="2"{% if user.avatar_type == 0 %} checked{% endif %}>Custom</label>
<span id="upload-thing"{% if not user.avatar_type == 0 %} class="none"{% endif %}>{% file_button %}</span>
<style>
.setting-avatar div.file-button-container { display: none; }
</style>
@@ -220,7 +175,6 @@
<div class="form-buttons">
<input type="submit" class="black-button apply-button" value="Save these settings">
</div>
{% endif %}
</ul></form></div></div>
{% endblock %}
-134
View File
@@ -78,140 +78,6 @@ def recaptcha_verify(request, key):
return False
return True
def video_upload(video):
hash = sha1()
for chunk in video.chunks():
hash.update(chunk)
imhash = hash.hexdigest()
# only either webm or mp4
extension = video.name[-4:]
if extension == '.mp4':
fname = imhash + '.mp4'
elif extension == 'webm':
fname = imhash + '.webm'
else:
return 1
# simply only write image if the hashed path doesn't already exist
if not os.path.exists(settings.MEDIA_ROOT + fname):
with open(settings.MEDIA_ROOT + fname, "wb+") as destination:
for chunk in video.chunks():
destination.write(chunk)
return settings.MEDIA_URL + fname
ImageFile.LOAD_TRUNCATED_IMAGES = True
def image_upload(img, stream=False, drawing=False, avatar=False, icon=False, banner=False):
Imagefield = False
if banner or icon:
Imagefield = True
if stream:
decodedimg = img.read()
else:
try:
decodedimg = base64.b64decode(img)
except ValueError:
return 1
if stream:
if not 'image' in img.content_type:
return 1
# upload svg?
#if 'svg' in mime:
#
try:
im = Image.open(io.BytesIO(decodedimg))
# OSError is probably from invalid images, SyntaxError probably from unsupported images
except (OSError, SyntaxError):
return 1
# Taken from https://coderwall.com/p/nax6gg/fix-jpeg-s-unexpectedly-rotating-when-saved-with-pil
if hasattr(im, '_getexif'):
orientation = 0x0112
exif = im._getexif()
if exif is not None:
orientation = exif.get(orientation)
rotations = {
3: Image.ROTATE_180,
6: Image.ROTATE_270,
8: Image.ROTATE_90
}
if orientation in rotations:
im = im.transpose(rotations[orientation])
if avatar or icon:
tiny = True
# crop 1:1
width, height = im.size
min_dimension = min(width, height)
crop_size = min_dimension
left = (width - crop_size) // 2
top = (height - crop_size) // 2
right = left + crop_size
bottom = top + crop_size
im = im.crop((left, top, right, bottom))
im.thumbnail((256, 256))
else:
tiny = False
im.thumbnail((1280, 1280))
# Let's check the aspect ratio and see if it's crazy
# IF this is a drawing
if drawing and ((im.size[0] / im.size[1]) < 0.30):
return 1
# I know some people have aneurysms when they see people actually using SHA1 in the real world, for anything in general.
# Yes, we are really using it. Sorry if that offends you. It's just fast and I don't feel I need anything more random, since we are talking about IMAGES.
if stream:
hash = sha1()
for chunk in img.chunks():
hash.update(chunk)
imhash = hash.hexdigest()
else:
imhash = sha1(im.tobytes()).hexdigest()
# File saving target
target = 'png'
if stream:
# If we have a stream and either a JPEG or a WEBP, save them as those since those are a bit better than plain PNG
if 'jpeg' in img.content_type:
target = 'jpeg'
im = im.convert('RGB')
elif 'webp' in img.content_type:
target = 'webp'
# Janky goofy ahh solution to a stupid problem!
if tiny:
floc = imhash + '_tiny' + '.' + target
else:
floc = imhash + '.' + target
# If the file exists, just use it, that's what hashes are for.
if not os.path.exists(settings.MEDIA_ROOT + floc):
im.save(settings.MEDIA_ROOT + floc, target, optimize=True)
if not Imagefield:
return settings.MEDIA_URL + floc
# Not compatible with imagefield otherwise, if this is not here, Images uploaded will not show due to an incorrect URL.
# yes it's a crack den solution but it works.
else: return floc
# Todo: Put this into post/comment delete thingy method
def image_rm(image_url):
if settings.IMAGE_DELETE_SETTING:
if settings.MEDIA_URL in image_url:
sysfile = image_url.split(settings.MEDIA_URL)[1]
sysloc = settings.MEDIA_ROOT + sysfile
if settings.IMAGE_DELETE_SETTING > 1:
try:
remove(sysloc)
except:
return False
else:
return True
# The RM'd directory to move it to
rmloc = sysloc.replace(settings.MEDIA_ROOT, settings.MEDIA_ROOT + 'rm/')
try:
rename(sysloc, rmloc)
except:
return False
else:
return True
else:
return False
def get_gravatar(email):
try:
page = urllib.request.urlopen('https://gravatar.com/avatar/'+ md5(email.encode('utf-8').lower()).hexdigest() +'?d=404&s=128')
+113 -236
View File
@@ -73,11 +73,15 @@ def community_list(request):
else:
ad = "no ads"
# announcements within the past week-ish
announcements = Post.objects.filter(community__tags='announcements', spoils=False, created__gte=Now()-timedelta(days=5)).order_by('-created')[:6]
if request.user.is_authenticated:
if request.user.show_announcements:
announcements = Post.objects.filter(community__tags='announcements', spoils=False, created__gte=Now()-timedelta(days=5)).order_by('-created')[:6]
else:
announcements = None
my_communities = obj.filter(creator=request.user).order_by('-created')[0:12]
else:
my_communities = None
announcements = None
return render(request, 'closedverse_main/community_list.html', {
'title': 'Communities',
'ad': ad,
@@ -180,25 +184,21 @@ def community_favorites(request):
})
def login_page(request):
# rn the password form does not take into account if you have the old password format or not.
if request.user.is_authenticated:
return redirect('/')
if request.method == 'POST':
incorrect_password = False
form = LoginForm(request.POST)
form = sign_in(request.POST)
if form.is_valid():
user = User.objects.get(username__iexact=form.cleaned_data['username'])
# no longer logs failed logins
# do I really want to fix that?
LoginAttempt.objects.create(user=user,success=True, user_agent=request.META.get('HTTP_USER_AGENT'),addr=request.META.get('REMOTE_ADDR'))
request.session['passwd'] = user.password
login(request, user)
authenticated_user, status = form.user
LoginAttempt.objects.create(user=authenticated_user, success=True, user_agent=request.META.get('HTTP_USER_AGENT'), addr=request.META.get('REMOTE_ADDR'))
request.session['passwd'] = authenticated_user.password
login(request, authenticated_user)
location = request.GET.get('next', '/')
if request.headers.get('x-requested-with') == 'XMLHttpRequest':
return HttpResponse(location)
return redirect(location)
else:
form = LoginForm()
form = sign_in()
return render(request, 'closedverse_main/login_page.html', {
'title': 'Log in',
@@ -420,159 +420,21 @@ def user_view(request, username):
user = request.user
profile = user.profile()
profile.setup(request)
comment_old = profile.comment
nickname_old = user.nickname
if profile.cannot_edit:
return json_response("Not allowed.")
if len(request.POST.get('screen_name')) == 0 or len(request.POST['screen_name']) > 32:
return json_response('Nickname is too long or too short (length '+str(len(request.POST.get('screen_name')))+', max 32)')
if len(request.POST.get('profile_comment')) > 2200:
return json_response('Profile comment is too long (length '+str(len(request.POST.get('profile_comment')))+', max 2200)')
if len(request.POST.get('country')) > 255:
return json_response('Region is too long (length '+str(len(request.POST.get('country')))+', max 255)')
if len(request.POST.get('website')) > 255:
return json_response('Web URL is too long (length '+str(len(request.POST.get('website')))+', max 255)')
if len(request.POST.get('bg_url')) > 300:
return json_response('Background URL is too long (length '+str(len(request.POST.get('bg_url')))+', max 300)')
if len(request.POST.get('whatareyou')) > 300:
return json_response('"What Are You" is too long (length '+str(len(request.POST.get('whatareyou')))+', max 300)')
if len(request.POST.get('external')) > 255:
return json_response('Discord Tag is too long (length '+str(len(request.POST.get('external')))+', max 300)')
if len(request.POST.get('email')) > 500:
return json_response('Email is too long (length '+str(len(request.POST.get('email')))+', max 500)')
# Kinda unneeded but gdsjkgdfsg
if request.POST.get('website') == 'Web URL' or request.POST.get('country') == 'Region' or request.POST.get('external') == 'Discord Tag':
return json_response("I'm laughing right now.")
form = profile_settings_page(request.POST, request.FILES)
if len(request.POST.get('avatar')) > 255:
return json_response('Avatar is too long (length '+str(len(request.POST.get('avatar')))+', max 255)')
if request.POST.get('email') and not request.POST.get('email') == 'None':
if User.email_in_use(request.POST['email'], request):
return HttpResponseBadRequest("That email address is already in use, that can't happen.")
try:
EmailValidator()(value=request.POST['email'])
except ValidationError:
return json_response("Your e-mail address is invalid. Input an e-mail address, or input nothing.")
if User.nnid_in_use(request.POST.get('origin_id'), request):
return json_response("That Nintendo Network ID is already in use, that would cause confusion.")
#if user.has_plain_avatar():
# user.avatar = request.POST.get('avatar') or ''
# custom handler
if request.POST.get('avatar') == '2':
if request.FILES.get('screen'):
if not request.user.has_freedom():
return json_response("Not allowed.")
upload = None
if request.FILES.get('screen'):
# worth noting that the file for the avatar is never cleaned up after the user changes it
upload = util.image_upload(request.FILES['screen'], True, avatar=True)
if upload == 1:
return json_response("sorry, we are racist to the image you uploaded, you have to choose another one")
user.avatar = upload
user.has_mh = False
elif request.POST.get('avatar') == '1':
if not request.POST.get('origin_id'):
user.has_mh = False
profile.origin_id = None
profile.origin_info = None
user.avatar = ('s' if getrandbits(1) else '')
user.avatar = get_gravatar(user.email) or ('s' if getrandbits(1) else '')
user.has_mh = False
elif request.POST.get('avatar') == '0':
if not request.POST.get('origin_id'):
user.has_mh = False
profile.origin_id = None
profile.origin_info = None
user.avatar = ('s' if getrandbits(1) else '')
else:
if not request.POST.get('mh'):
return json_response('i think you gotta wait for the nnid to retrieve')
user.has_mh = True
#getmii = get_mii(request.POST.get('origin_id'))
#if not getmii:
# return json_response('NNID not found')
user.avatar = request.POST.get('mh')
#profile.origin_id = getmii[2]
profile.origin_id = request.POST['origin_id']
profile.origin_info = json.dumps([request.POST.get('mh'), 'if you see this then something is wrong', request.POST['origin_id']])
# set the username color
if request.POST.get('color'):
try:
validate_color(request.POST['color'])
except ValidationError:
user.color = None
else:
dark = True if user.color == '#000000' else False
light = True if user.color == '#ffffff' else False
if dark:
return json_response("Too dark")
elif light:
user.color = None
else:
if request.POST['color'] == '#ffffff':
user.color = None
else:
user.color = request.POST['color']
else:
user.color = None
# set the theme
if request.POST.get('theme'):
reset_theme = False if request.POST.get('reset-theme') is None else True
try:
validate_color(request.POST['theme'])
except ValidationError:
user.theme = None
else:
light = True if request.POST['theme'] == '#ffffff' else False
if light:
user.theme = None
elif reset_theme:
user.theme = None
else:
user.theme = request.POST['theme']
else:
user.theme = None
if request.POST.get('email') == 'None':
user.email = None
else:
user.email = request.POST.get('email')
profile.country = request.POST.get('country')
website = request.POST.get('website')
if ' ' in website or not '.' in website:
profile.weblink = ''
else:
profile.weblink = website
profile.comment = request.POST.get('profile_comment')
profile.external = request.POST.get('external')
profile.whatareyou = request.POST.get('whatareyou')
profile.relationship_visibility = (request.POST.get('relationship_visibility') or 0)
profile.id_visibility = (request.POST.get('id_visibility') or 0)
profile.yeahs_visibility = (request.POST.get('yeahs_visibility') or 0)
profile.pronoun_is = (request.POST.get('pronoun_dot_is') or 0)
profile.gender_is = (request.POST.get('gender_select') or 0)
profile.comments_visibility = (request.POST.get('comments_visibility') or 0)
profile.let_friendrequest = (request.POST.get('let_friendrequest') or 0)
user.bg_url = (request.POST.get('bg_url') or None)
user.nickname = filterchars(request.POST.get('screen_name'))
# Maybe todo?: Replace all "not .. == .." with ".. != .." etc
# If the user cannot edit and their nickname/avatar is different than what they had, don't let it happen.
if request.POST.get('profile_comment') != comment_old or request.POST.get('screen_name') != nickname_old:
ProfileHistory.objects.create(user=user,
old_nickname=nickname_old,
old_comment=comment_old,
new_nickname=request.POST.get('screen_name'),
new_comment=request.POST.get('profile_comment'))
if not user.email:
profile.email_login = 1
else:
profile.email_login = (request.POST.get('email_login') or 1)
profile.save()
user.save()
if form.is_valid():
email = form.cleaned_data.get('email')
file = form.cleaned_data.get('file')
if email and User.objects.exclude(pk=user.pk).filter(email__iexact=email).exists():
return json_response('Email is taken already.')
if file and not profile.let_freedom:
return json_response('You aren\'t allowed to upload images.')
# custom save function, because I am clinically insane
form.save(user=request.user)
return HttpResponse()
else:
return json_response(form.errors.as_text())
posts = user.get_posts(3, 0, request, timezone.now())
yeahed = user.get_yeahed(0, 3, 0, request.user)
for yeah in yeahed:
@@ -979,7 +841,7 @@ def community_tools(request, community):
can_edit = the_community.can_edit_community(request.user)
if not can_edit:
raise Http404()
form = CommunitySettingForm(instance=the_community)
form = edit_community(instance=the_community)
return render(request, 'closedverse_main/community_tools.html', {
'title': 'Community tools',
'form': form,
@@ -994,7 +856,7 @@ def community_tools_set(request, community):
can_edit = the_community.can_edit_community(request.user)
if not can_edit:
return HttpResponseForbidden()
form = CommunitySettingForm(request.POST, request.FILES, instance=the_community)
form = edit_community(request.POST, request.FILES, instance=the_community)
if not form.is_valid():
return json_response(form.errors.as_text())
form.save()
@@ -1010,7 +872,7 @@ def community_create(request):
raise Http404()
if request.user.c_tokens < 1:
raise Http404()
form = CommunitySettingForm()
form = edit_community()
return render(request, 'closedverse_main/community_create.html', {
'title': 'Create a community',
'form': form,
@@ -1022,7 +884,7 @@ def community_create_action(request):
return HttpResponseForbidden()
if request.user.c_tokens < 1:
return HttpResponseForbidden()
form = CommunitySettingForm(request.POST, request.FILES)
form = edit_community(request.POST, request.FILES)
if not form.is_valid():
return json_response(form.errors.as_text())
community = form.save()
@@ -1043,29 +905,28 @@ def post_create(request, community):
community = Community.objects.get(id=community)
except (Community.DoesNotExist, ValueError):
return HttpResponseNotFound()
# Method of Community
new_post = community.create_post(request)
if not new_post:
return HttpResponseBadRequest()
if isinstance(new_post, int):
# If post limit
if new_post == 8:
# then do meme
return json_response("You have already exceeded the number of posts that you can contribute in a single day. Please try again tomorrow.", 1215919)
return json_response({
1: "Your post is too long ("+str(len(request.POST['body']))+" characters, 2200 max).",
2: "The image you've uploaded is invalid.",
3: "You're posting too quickly, wait a few seconds and try again.",
4: "Apparently, you're not allowed to post here.",
5: "Uh-oh, that URL wasn't valid..",
6: "Not allowed.",
7: "Please don't spam.",
9: "You're very funny. Unfortunately your funniness blah blah blah fuck off.",
10: "No mr white, you can't make a post entirely consistant of spaces",
11: "The video you've uploaded is invalid.",
12: "Please don't post Zalgo text.",
13: "Please check your notifications.",
}.get(new_post))
if not community.post_perm(request):
return json_response('You are not allowed to post here.')
if request.user.unread_warning():
return json_response('Please check your notifications.')
limit = request.user.limit_remaining()
if not limit is False and not limit > 0:
return json_response('You are out of posts for today.')
del(limit)
if Post.real.filter(creator=request.user, created__gt=timezone.now() - timedelta(seconds=10)).exists():
return json_response('Please slow down.')
form = post_form(request.POST, request.FILES)
if form.is_valid():
if not request.user.has_freedom() and form.cleaned_data.get('file'):
return json_response('You\'re not allowed to post files.')
new_post = form.save(commit=False)
new_post.creator = request.user
new_post.community = community
new_post.save()
else:
return json_response(form.errors.as_text())
# Render correctly whether we're posting to Activity Feed
if community.is_activity():
return render(request, 'closedverse_main/elements/community_post.html', {
@@ -1183,23 +1044,30 @@ def post_comments(request, post):
if request.method == 'POST':
# Wake
request.user.wake(request.META['REMOTE_ADDR'])
# Method of Post
new_post = post.create_comment(request)
if not new_post:
return HttpResponseBadRequest()
if isinstance(new_post, int):
# If post limit
if new_post == 8:
# then do meme
return json_response("You have already exceeded the number of posts that you can contribute in a single day. Please try again tomorrow.", 1215919)
return json_response({
1: "Your comment is too long ("+str(len(request.POST['body']))+" characters, 2200 max).",
2: "The image you've uploaded is invalid.",
3: "You're making comments too fast, wait a few seconds and try again.",
6: "Not allowed.",
12: "Please don't post Zalgo text.",
13: "Please check your notifications.",
}.get(new_post))
# using forms now
if not post.can_comment(request):
return json_response('You are not allowed to comment here.')
if request.user.unread_warning():
return json_response('Please check your notifications.')
limit = request.user.limit_remaining()
if not limit is False and not limit > 0:
return json_response('You are out of posts for today.')
del(limit)
if Comment.real.filter(creator=request.user, created__gt=timezone.now() - timedelta(seconds=10)).exists():
return json_response('Please slow down.')
form = comment_form(request.POST, request.FILES)
if form.is_valid():
if not request.user.has_freedom() and form.cleaned_data.get('file'):
return json_response('You\'re not allowed to post files.')
new_comment = form.save(commit=False)
new_comment.creator = request.user
new_comment.community = post.community
new_comment.original_post = post
new_comment.save()
else:
return json_response(form.errors.as_text())
# Give the notification!
if post.is_mine(request.user):
users = []
@@ -1211,7 +1079,7 @@ def post_comments(request, post):
Notification.give_notification(request.user, 3, user, post)
else:
Notification.give_notification(request.user, 2, post.creator, post)
return render(request, 'closedverse_main/elements/post-comment.html', { 'comment': new_post })
return render(request, 'closedverse_main/elements/post-comment.html', { 'comment': new_comment })
else:
comment_count = post.number_comments()
if comment_count > 20:
@@ -1517,19 +1385,24 @@ def messages_view(request, username):
if request.method == 'POST':
# Wake
request.user.wake(request.META['REMOTE_ADDR'])
new_post = conversation.make_message(request)
if not new_post:
return HttpResponseBadRequest()
if isinstance(new_post, int):
return json_response({
1: "Your message is too long ("+str(len(request.POST['body']))+" characters, 2200 max).",
2: "The image you've uploaded is invalid.",
3: "Sorry, but you're sending messages too fast.",
4: "Please check your notifications.",
6: "Not allowed.",
}.get(new_post))
if request.user.unread_warning():
return json_response('Please check your notifications.')
if Post.real.filter(creator=request.user, created__gt=timezone.now() - timedelta(seconds=3)).exists():
return json_response('Please slow down.')
form = message_form(request.POST, request.FILES)
if form.is_valid():
if not request.user.has_freedom() and form.cleaned_data.get('file'):
return json_response('You\'re not allowed to post files.')
new_message = form.save(commit=False)
new_message.creator = request.user
new_message.conversation = conversation
new_message.save()
else:
return json_response(form.errors.as_text())
friendship.update()
return render(request, 'closedverse_main/elements/message.html', { 'message': new_post })
return render(request, 'closedverse_main/elements/message.html', { 'message': new_message })
else:
if request.GET.get('offset'):
messages = conversation.messages(request, 20, int(request.GET['offset']))
@@ -1591,11 +1464,15 @@ def prefs(request):
request.user.hide_online = True
else:
request.user.hide_online = False
if request.POST.get('c'):
request.user.show_announcements = True
else:
request.user.show_announcements = False
profile.save()
request.user.save()
return HttpResponse()
lights = not (request.session.get('lights', False))
arr = [profile.let_yeahnotifs, lights, request.user.hide_online]
arr = [profile.let_yeahnotifs, lights, request.user.hide_online, request.user.show_announcements]
return JsonResponse(arr, safe=False)
@login_required
@@ -1609,9 +1486,9 @@ def user_tools(request, username):
# check if the requesting user is allowed to change someone
if user.has_authority(request.user):
return HttpResponseForbidden()
user_form = User_tools_Form(instance=user)
profile_form = Profile_tools_Form(instance=profile)
purge_form = PurgeForm()
user_form = manage_user(instance=user)
profile_form = manage_profile(instance=profile)
purge_form = purge_user()
accountmatch = User.objects.filter(
Q(addr=user.addr) | Q(addr=user.signup_addr)
@@ -1678,7 +1555,7 @@ def user_tools_warnings(request, username):
if user.has_authority(request.user):
return HttpResponseForbidden()
if request.method == 'POST':
form = Give_warning_form(request.POST)
form = give_warning(request.POST)
if form.is_valid():
warning = form.save(commit=False)
warning.to = user
@@ -1687,7 +1564,7 @@ def user_tools_warnings(request, username):
return redirect('main:user-view', user)
unread_warnings = Notification.objects.filter(type=5, to=user, read=False)[:3]
all_warnings = Warning.objects.filter(to=user).order_by('-id')[:8]
form = Give_warning_form()
form = give_warning()
return render(request, 'closedverse_main/man/manage_warnings.html', {
'user': user,
'unread_warnings': unread_warnings,
@@ -1707,7 +1584,7 @@ def user_tools_bans(request, username):
return HttpResponseForbidden()
if request.method == 'POST':
if not user.banned():
form = Give_Ban_Form(request.POST)
form = give_ban(request.POST)
if form.is_valid():
ban = form.save(commit=False)
ban.to = user
@@ -1717,15 +1594,15 @@ def user_tools_bans(request, username):
AuditLog.objects.create(type=5, user=user, by=request.user)
return redirect('main:user-view', user)
else:
form = Give_Ban_Form_Edit(request.POST, instance=user.active_ban())
form = edit_ban(request.POST, instance=user.active_ban())
ban = form.save(commit=False)
ban.save()
AuditLog.objects.create(type=6, user=user, by=request.user)
return redirect('main:user-view', user)
if not user.banned():
form = Give_Ban_Form()
form = give_ban()
else:
form = Give_Ban_Form_Edit(instance=user.active_ban())
form = edit_ban(instance=user.active_ban())
return render(request, 'closedverse_main/man/manage_bans.html', {
'user': user,
'banned': user.banned(),
@@ -1746,9 +1623,9 @@ def user_tools_set(request, username):
if user.has_authority(request.user):
return HttpResponseForbidden()
user_form = User_tools_Form(request.POST, instance=user)
profile_form = Profile_tools_Form(request.POST, instance=profile)
purge_form = PurgeForm(request.POST)
user_form = manage_user(request.POST, instance=user)
profile_form = manage_profile(request.POST, instance=profile)
purge_form = purge_user(request.POST)
if purge_form.is_valid():
purge_posts = purge_form.cleaned_data["purge_posts"]
@@ -1762,9 +1639,9 @@ def user_tools_set(request, username):
Post.real.filter(creator=user, status=5, is_rm=True).update(is_rm=False, status=0)
Comment.real.filter(creator=user, status=5, is_rm=True).update(is_rm=False, status=0)
if purge_posts == True:
Post.real.filter(creator=user).update(is_rm=True, status=5)
Post.real.filter(creator=user, is_rm=False).update(is_rm=True, status=5)
if purge_comments == True:
Comment.real.filter(creator=user).update(is_rm=True, status=5)
Comment.real.filter(creator=user, is_rm=False).update(is_rm=True, status=5)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
@@ -1876,7 +1753,7 @@ def my_data(request):
def change_password(request):
user = request.user
if request.method == 'POST':
form = Settomgs_Change_Password(request.POST)
form = set_password(request.POST)
if form.is_valid():
old = form.cleaned_data.get('Old_Password')
new = form.cleaned_data.get('New_Password')
@@ -1888,7 +1765,7 @@ def change_password(request):
return json_response("Success! Now you can log in with your new password!", "Finished")
return json_response(form.errors.as_text())
else:
form = Settomgs_Change_Password(request.POST)
form = set_password(request.POST)
return render(request, 'closedverse_main/change-password.html', {
'user': user,
'form': form,
+68 -225
View File
@@ -10,218 +10,6 @@ var pjax_container = '#container';
// Edit: This used to match 'WebKit' but it's now Gecko because WebKit will have that in here anyway
var webkit = navigator.userAgent.indexOf('WebKit') > 0 || navigator.userAgent.indexOf('Firefox') > 0;
function setupDrawboard() {
var canvas = document.getElementById("artwork-canvas");
var ctx = canvas.getContext('2d');
var haval = $("input[type=hidden][name=painting]");
if(haval.length) {
dds = new Image();
dds.src = "data:image/png;base64," + haval.val();
dds.onload = function() {
ctx.drawImage(dds,0,0);
};
}
var undoCanvas = document.getElementById('artwork-canvas-undo');
var undoCtx = undoCanvas.getContext('2d');
var redoCanvas = document.getElementById('artwork-canvas-redo');
var redoCtx = redoCanvas.getContext('2d');
undoCanvas.width = 320; undoCanvas.height = 120;
redoCanvas.width = 320; redoCanvas.height = 120;
canvas.width = 320; canvas.height = 120;
var mousePosOld = 0;
var artworkTool = {type: 0, size: 1};
var sizeSmall = 1;
var sizeMedium = 2;
var sizeLarge = 4;
var artworkColor = "#000000";
var artworkZoomFactor = 1;
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect();
if(evt.type == 'touchmove') {
var clientX = evt.touches[0].clientX;
var clientY = evt.touches[0].clientY;
} else {
var clientX = evt.clientX;
var clientY = evt.clientY;
}
return {
x: (clientX - rect.left) / artworkZoomFactor,
y: (clientY - rect.top) / artworkZoomFactor
};
}
function drawLineNoAliasing(ctx, sx, sy, tx, ty) {
var dist = Math.sqrt((tx-sx)*(tx-sx)+(ty-sy)*(ty-sy));
var ang = Math.atan((ty-sy)/((tx-sx)==0?0.01:(tx-sx)))+((tx-sx)<0?Math.PI:0);
for(var i=0;i<dist;i++) {
ctx.fillRect(Math.round(sx + Math.cos(ang)*i),
Math.round(sy + Math.sin(ang)*i),
artworkTool.size, artworkTool.size);
}
}
function artworkUpdate(evt) {
//console.log('artworkUpdate()');
var mousePos = getMousePos(evt);
if(artworkTool.type < 2) {
if(mousePosOld == 0) mousePosOld = mousePos;
if(evt.originalEvent.buttons == 1 || evt.type == 'touchmove') {
if(artworkTool.type == 0) {
ctx.fillStyle = artworkColor;
} else {
ctx.fillStyle = "#fff";
}
drawLineNoAliasing(ctx, mousePosOld.x,mousePosOld.y,mousePos.x,mousePos.y);
}
mousePosOld = mousePos;
}
}
function artworkDrawOnce(evt) {
//console.log('artworkDrawOnce()');
var mousePos = getMousePos(evt);
if(artworkTool.type < 2) {
if(evt.which == 1) {
if(artworkTool.type == 0) {
ctx.fillStyle = artworkColor;
} else {
ctx.fillStyle = "#fff";
}
ctx.fillRect(Math.round(mousePos.x), Math.round(mousePos.y), artworkTool.size, artworkTool.size);
}
} else {
ctx.fillStyle = artworkColor;
ctx.fillFlood(mousePos.x, mousePos.y, 0);
}
}
function artworkClear() {
doTheThing();
//console.log('artworkClear()');
artworkUndoDown();
//console.log('artworkUndoDown()');
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, 320, 120);
}
function artworkUndoDown() {
undoCtx.drawImage(canvas, 0, 0);
}
function artworkUndo() {
redoCtx.drawImage(canvas, 0, 0);
ctx.drawImage(undoCanvas, 0, 0);
undoCtx.drawImage(redoCanvas, 0, 0);
}
function artworkToolUpdate() {
//console.log('artworkToolUpdate()');
if($(this).hasClass('artwork-eraser')) {
var toolType = 1;
} else if($(this).hasClass('artwork-fill')) {
var toolType = 2;
} else {
var toolType = 0;
}
if($(this).hasClass('selected')) {
if($(this).hasClass('small')) {
artworkTool = {type: toolType, size: sizeMedium};
$(this).removeClass('small');
$(this).addClass('medium');
} else if ($(this).hasClass('medium')) {
artworkTool = {type: toolType, size: sizeLarge};
$(this).removeClass('medium');
$(this).addClass('large');
} else if ($(this).hasClass('large')) {
artworkTool = {type: toolType, size: sizeSmall};
$(this).removeClass('large');
$(this).addClass('small');
} else {
artworkTool = {type: toolType};
}
} else {
$('.memo-buttons button').removeClass('selected');
$(this).addClass('selected');
if($(this).hasClass('small')) {
artworkTool = {type: toolType, size: sizeSmall};
} else if ($(this).hasClass('medium')) {
artworkTool = {type: toolType, size: sizeMedium};
} else if ($(this).hasClass('large')) {
artworkTool = {type: toolType, size: sizeLarge};
} else {
artworkTool = {type: toolType};
}
}
}
function artworkZoomUpdate(evt) {
//console.log('artworkZoomUpdate()');
if(artworkZoomFactor == 1) {
artworkZoomFactor = 2;
$('#artwork-canvas').css('width', '640px');
} else if(artworkZoomFactor == 2) {
artworkZoomFactor = 4;
$('#artwork-canvas').css('width', '1280px');
$(this).addClass('out');
} else {
artworkZoomFactor = 1;
$('#artwork-canvas').css('width', '320px');
$(this).removeClass('out');
}
}
artworkClear();
$(document).on('mousemove', artworkUpdate);
$(document).on('touchstart', function(){
mousePosOld = 0;
});
$(document).on('touchmove', artworkUpdate);
$('#artwork-canvas').on('mousedown', artworkDrawOnce);
$('#artwork-canvas').on('mousedown', artworkUndoDown);
$('button').contextmenu(function() { $(this).click(); return false });
$('.artwork-clear').click(artworkClear);
$('.artwork-undo').click(artworkUndo);
$('.artwork-pencil, .artwork-eraser, .artwork-fill').click(artworkToolUpdate);
$(".artwork-color").spectrum({
color: "#000000",
preferredFormat: "hex",
showInput: true,
showPalette: true,
change: function(color) {
artworkColor = color;
},
palette: [["#000000", "#808080"], ["#ff0000",
"#804000"], ["#ff8000", "#c08000"], ["#ffff00",
"#fff8dc"], ["#00c700", "#008000"], ["#00ffff",
"#00a0a0"], ["#0000ff", "#0080ff"], ["#ff00ff",
"#800080"]]
});
$('.artwork-zoom').click(artworkZoomUpdate);
function doTheThing(){
var canvas = document.getElementById("artwork-canvas");
var ctx = canvas.getContext('2d');
//console.log('doing the thing');
var pixelArray = [];
for(var i = 0; i < canvas.clientHeight; i++) {
pixelArray[i] = [];
for(var j = 0; j < canvas.clientWidth; j++) {
var data = ctx.getImageData(j, i, 1, 1).data;
if(data[0] + data[1] + data[2] < 382) {
pixelArray[i].push(0);
} else {
pixelArray[i].push(1);
}
}
}
}
$('.memo-finish-btn').on('click',function(){
var dataURL = canvas.toDataURL();
if(typeof dataURL !== undefined) {
var img = dataURL.split(",")[1];
//$("input[type=hidden][name=painting]").val(b.Closed.chksum(img) + img);
$("input[type=hidden][name=painting]").val(img);
}
$("#drawing").remove();
$(".textarea-memo").append("<img id=\"drawing\" src=\"" + dataURL + "\" style=\"background:white;\"></img>");
$("#memo-drawboard-page button").off('click');
$('body').css('overflow', '');
});
}
var fixe = false;
function openDrawboardModal() {
if(innerWidth <= 800) {
@@ -441,6 +229,10 @@ var Olv = Olv || {};
}
var g = new Olv.ModalWindow($('.linkc'));g.open();
},
dlConf: function() {
$('#container').prepend('<div class="dialog linkc none"><div class=dialog-inner><div class=window><h1 class=window-title>Download file?</h1><div class=window-body><p class=window-body-content>You are about to download a file.<br>Are you sure you want to download <b>'+ass+'</b>?</p><div class=form-buttons><button class="olv-modal-close-button gray-button" type=button data-event-type=ok onclick="$(\'.linkc\').remove()">No</button><button class="olv-modal-close-button black-button" type=button onclick="Olv.Net.lo(\''+ass+'\');$(\'.linkc\').remove()">Yes</button></div></div></div></div></div>');
var g = new Olv.ModalWindow($('.linkc'));g.open();
},
changesel: function(a) {
$("li#global-menu-" + a).addClass("selected");
}
@@ -504,6 +296,11 @@ var Olv = Olv || {};
error_code: "Not Allowed (Forbidden)",
message: "Try refreshing the page or logging back in.\n"
}
case 413:
return {
error_code: "File size too large.",
message: "Big brother it's too big, I can't take it!\n"
}
break;
case 500:
errmsg = "An error has been encountered in the server.\n";
@@ -1473,6 +1270,11 @@ var Olv = Olv || {};
g.preventDefault();
b.Closed.prlinkConf();
});
a('.download-confirm').on('click', function(g) {
ass = a(this).attr('href');
g.preventDefault();
b.Closed.dlConf();
});
}
,
b.Entry.toggleEmpathy = function(a) {
@@ -2351,11 +2153,54 @@ var Olv = Olv || {};
e.preventDefault();
$.ajax({url: '/pref',
success: function(a) {
// Assigning 'checked' attribute based on the response array 'a'
yeah_notifications = a[0] ? ' checked' : '';
lights_off = a[1] ? ' checked' : '';
online_status = a[2] ? ' checked' : '';
show_announcements = a[3] ? ' checked' : '';
$('#wrapper').prepend('<div class="dialog acc-set none"><div class=dialog-inner><div class=window><h1 class=window-title>Account preferences</h1><div class=window-body><form id=feedback-form><p class=window-body-content> These are your account\'s preferences, pretty self-explanatory.</p><br> <input type=checkbox value=1 name=a'+ yeah_notifications +'> Enable notifications for Yeahs<br><input type=checkbox onclick=Olv.Closed.lights()'+ lights_off +'> Enable dark mode<br> <input type=checkbox value=1 name=b'+ online_status +'> Hide my latest time seen and online status from others</form><div class=form-buttons><button class="olv-modal-close-button gray-button" type=button data-event-type=ok onclick="$(\'.acc-set\').remove()">Cancel</button><button class="black-button ac-send" type="button">Save</button></div></div></div></div></div>');
// Prepending the form HTML to the '#wrapper' element
$('#wrapper').prepend(`
<div class="dialog acc-set none">
<div class="dialog-inner">
<div class="window">
<h1 class="window-title">Account preferences</h1>
<div class="window-body">
<form id="feedback-form">
<p class="window-body-content">
These are your account's preferences, pretty self-explanatory.
</p>
<br>
<input type="checkbox" value="1" name="a"${yeah_notifications}>
Enable notifications for Yeahs
<br>
<input type="checkbox" onclick="Olv.Closed.lights()"${lights_off}>
Enable dark mode
<br>
<input type="checkbox" value="1" name="b"${online_status}>
Hide my latest time seen and online status from others
<br>
<input type="checkbox" value="1" name="c"${show_announcements}>
Show the announcements list on the front page.
</form>
<div class="form-buttons">
<button
class="olv-modal-close-button gray-button"
type="button"
data-event-type="ok"
onclick="$('.acc-set').remove()"
>
Cancel
</button>
<button class="black-button ac-send" type="button">
Save
</button>
</div>
</div>
</div>
</div>
</div>
`);
var g = new b.ModalWindow($('.acc-set'));g.open();
$('.ac-send').on('click',function() {
b.Form.post('/pref', $('#feedback-form').serializeArray())
@@ -2734,6 +2579,11 @@ mode_post = 0;
msg_rm_load();
});
});
$('.download-confirm').on('click', function(g) {
ass = $(this).attr('href');
g.preventDefault();
b.Closed.dlConf();
});
}),
b.router.connect("^/communities/(?:favorites|played)$", function(a, c, d) {
b.Closed.changesel("community");
@@ -2926,16 +2776,8 @@ $('.post-poll .poll-votes').on('click', function() {
$('.cancel-button').on('click',function(){et()})
b.EntryForm.setupFormStatus(t, e);
$('.edit-post-button').on('click',function(){
if($('.post-content-memo').length) {
b.showMessage("", "You can't edit a drawing, sorry.");
} if($('.screenshot-container > video').length) {
b.showMessage("", "You can't edit your uploaded video, sorry. But you can edit the contents of the post.");
et();
b.Form.toggleDisabled(submit_btn, true);
} else {
et();
b.Form.toggleDisabled(submit_btn, true);
}
})
submit_btn.on('click',function(a) {
a.preventDefault();
@@ -3045,12 +2887,8 @@ mode_post = 0;
$('.cancel-button').on('click',function(){et()})
b.EntryForm.setupFormStatus(t, e);
$('.edit-post-button').on('click',function(){
if($('.reply-content-memo').length) {
b.showMessage("", "You can't edit a drawing, sorry.")
} else {
et();
b.Form.toggleDisabled(submit_btn, true)
}
})
submit_btn.on('click',function(a) {
a.preventDefault()
@@ -3061,6 +2899,11 @@ mode_post = 0;
})
})
}
a('.download-confirm').on('click', function(g) {
ass = a(this).attr('href');
g.preventDefault();
b.Closed.dlConf();
});
rm_btn = $('.rm-post-button')
if(rm_btn.length) {
rm_btn.on('click',function(){