mirror of
https://github.com/Mistake35/Cedar-Django.git
synced 2026-07-17 16:11:14 +10:00
1879 lines
74 KiB
Python
1879 lines
74 KiB
Python
from __future__ import unicode_literals
|
|
from django.db import models
|
|
from django.contrib.auth.base_user import AbstractBaseUser
|
|
from django.contrib.auth.models import BaseUserManager
|
|
from django.db.models import Q, QuerySet, Max, F, Count, Case, When, Exists, OuterRef, Subquery
|
|
from django.utils import timezone
|
|
from django.core.validators import RegexValidator, URLValidator
|
|
from django.core.exceptions import ValidationError
|
|
from datetime import timedelta, time
|
|
#from passlib.hash import bcrypt_sha256
|
|
# you may want to import django.conf.settings instead (then change checks for DEFAULT_FROM_EMAIL)
|
|
from closedverse import settings
|
|
from closedverse_main.context_processors import brand_name, brand_logo
|
|
from . import util
|
|
from random import getrandbits
|
|
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 re
|
|
import unicodedata
|
|
import random
|
|
|
|
feelings = ((0, 'normal'), (1, 'happy'), (2, 'wink'), (3, 'surprised'), (4, 'frustrated'), (5, 'confused'), (38, 'japan'), (39, 'lol i lied'), (69, 'adam is gay'), (70, 'I am a faggot!'), (71, 'Juice'), (72, "Commit Suicide"), (73, "Fresh!"))
|
|
post_status = ((0, 'ok'), (1, 'delete by user'), (2, 'delete by authority'), (3, 'delete by mod'), (4, 'delete by admin'), (5, 'account pruge'))
|
|
visibility = ((0, 'show'), (1, 'friends only'), (2, 'hide'), )
|
|
|
|
# Like set() but orders
|
|
def organ(seq):
|
|
seen = set()
|
|
seen_add = seen.add
|
|
return [x for x in seq if not (x in seen or seen_add(x))]
|
|
|
|
class UserManager(BaseUserManager):
|
|
# idk why this is even here
|
|
def create_user(self, username, password):
|
|
user = self.model(
|
|
username=username,
|
|
)
|
|
user.set_password(password)
|
|
user.save(using=self._db)
|
|
return user
|
|
|
|
def closed_create_user(self, username, password, email, addr, signup_addr, nick, nn, gravatar):
|
|
user = self.model(
|
|
username = username,
|
|
nickname = util.filterchars(nick),
|
|
addr = addr,
|
|
signup_addr = signup_addr,
|
|
email = email,
|
|
)
|
|
profile = Profile.objects.model()
|
|
if nn:
|
|
user.avatar = nn[0]
|
|
profile.origin_id = nn[2]
|
|
profile.origin_info = json.dumps(nn)
|
|
user.has_mh = True
|
|
else:
|
|
user.avatar = util.get_gravatar(email) or ('s' if getrandbits(1) else '')
|
|
|
|
user.has_mh = False
|
|
user.set_password(password)
|
|
user.save(using=self._db)
|
|
profile.user = user
|
|
profile.save()
|
|
return user
|
|
def create_superuser(self, username, password):
|
|
user = self.model(
|
|
username=username,
|
|
)
|
|
user.set_password(password)
|
|
user.staff = True
|
|
user.level = 99 # added because some admin funcs are missing otherwise
|
|
user.save()
|
|
profile = Profile.objects.model()
|
|
profile.user = user
|
|
profile.save()
|
|
return user
|
|
def authenticate(self, username, password):
|
|
if not username or username.isspace():
|
|
return None
|
|
user = self.filter(Q(username__iexact=username.replace(' ', '')) | Q(username__iexact=username) | Q(email=username))
|
|
if not user.exists():
|
|
return None
|
|
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
|
|
except ValueError:
|
|
return (user, 2)
|
|
else:
|
|
if not passwd:
|
|
#if user.can_manage():
|
|
# return None
|
|
return (user, False)
|
|
return (user, True)
|
|
|
|
class PostManager(models.Manager):
|
|
def get_queryset(self):
|
|
return super(PostManager, self).get_queryset().filter(is_rm=False)
|
|
|
|
class CommunityFavoriteManager(models.Manager):
|
|
def get_queryset(self):
|
|
return super(CommunityFavoriteManager, self).get_queryset().filter(community__is_rm=False).exclude(community__type=4)
|
|
|
|
# Taken from https://github.com/jaredly/django-colorfield/blob/master/colorfield/fields.py
|
|
color_re = re.compile('^#([A-Fa-f0-9]{6})$')
|
|
validate_color = RegexValidator(color_re, "Enter a valid color", 'invalid')
|
|
class ColorField(models.CharField):
|
|
default_validators = [validate_color]
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['max_length'] = 18
|
|
super(ColorField, self).__init__(*args, **kwargs)
|
|
|
|
# custom role in db
|
|
class Role(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
# determines whether to fetch role from static or media, for built-in roles
|
|
is_static = models.BooleanField(default=False)
|
|
image = models.ImageField(upload_to='roles/', max_length=100, help_text='Upload an icon that will show on the top left of one\'s profile. A 22x22 image works best!')
|
|
organization = models.CharField(max_length=255, blank=True, null=True, help_text='Text that shows above one\'s username')
|
|
|
|
def __str__(self):
|
|
return "role \"" + str(self.organization) + "\""
|
|
|
|
#mii_domain = 'https://mii-secure.cdn.nintendo.net'
|
|
# 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/'
|
|
|
|
class User(AbstractBaseUser):
|
|
id = models.AutoField(primary_key=True)
|
|
username = models.CharField(max_length=32, unique=True)
|
|
# Todo: Don't allow nickname to be null (once this is fixed, un-str-ify line 357 of views; the one with ogdata description)
|
|
# Also don't let lots of other strings to be null
|
|
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)
|
|
# LEVEL: 0-1 is default, everything else is just levels
|
|
level = models.SmallIntegerField(default=0, help_text='The rank of the user, \"0\" is by default. Users with a high enough rank will be able to manage users.')
|
|
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.')
|
|
addr = models.CharField(max_length=64, null=True, blank=True)
|
|
signup_addr = models.CharField(max_length=64, null=True, blank=True)
|
|
# 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
|
|
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)
|
|
|
|
staff = models.BooleanField(default=False, help_text='Allow this user to access the admin panel you\'re using right now?')
|
|
active = models.BooleanField(default=True, help_text='If this is off, the user is basically banned and can\'t do shit here')
|
|
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.')
|
|
warned = models.BooleanField(default=False, help_text='Shows an annoying fucking warning box on the front page.')
|
|
warned_reason = models.CharField(blank=True, null=True, max_length=600, help_text='This string will show if the user is banned, or warned.')
|
|
bg_url = models.CharField(max_length=300, null=True, blank=True)
|
|
|
|
is_anonymous = False
|
|
is_authenticated = True
|
|
|
|
last_login = models.DateTimeField(auto_now=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
USERNAME_FIELD = 'username'
|
|
EMAIL_FIELD = 'email'
|
|
REQUIRED_FIELDS = []
|
|
|
|
objects = UserManager()
|
|
|
|
def __str__(self):
|
|
return self.username
|
|
def get_full_name(self):
|
|
return self.username
|
|
def get_short_name(self):
|
|
return self.nickname
|
|
def get_username(self):
|
|
return self.username
|
|
def has_module_perms(self, a):
|
|
return True
|
|
def has_perm(self, a):
|
|
return self.staff
|
|
def is_staff(self):
|
|
return self.staff
|
|
def is_active(self):
|
|
return self.active
|
|
def is_warned(self):
|
|
return self.warned
|
|
def get_warned_reason(self):
|
|
return self.warned_reason
|
|
def profile(self, thing=None):
|
|
# If thing is specified, that field is retrieved
|
|
if thing:
|
|
# could this be shortened? or discontinued in general
|
|
return self.profile_set.all().values_list(thing, flat=True).first()
|
|
# Otherwise just get full profile
|
|
return self.profile_set.filter().first()
|
|
def gravatar(self):
|
|
g = util.get_gravatar(self.email)
|
|
if not g:
|
|
return settings.STATIC_URL + 'img/anonymous-mii.png'
|
|
return g
|
|
def banned(self):
|
|
banned = self.banned_user.filter(active=True, expiry_date__gt=timezone.now())
|
|
return banned.exists()
|
|
def active_ban(self):
|
|
ban = self.banned_user.filter(active=True, expiry_date__gt=timezone.now()).order_by('-created').first()
|
|
return ban
|
|
def mh(self):
|
|
origin_info = self.profile('origin_info')
|
|
if not origin_info:
|
|
return None
|
|
try:
|
|
infodecode = json.loads(origin_info)
|
|
except:
|
|
return None
|
|
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:
|
|
return False
|
|
return True
|
|
def let_yeahnotifs(self):
|
|
return self.profile('let_yeahnotifs')
|
|
def limit_remaining(self):
|
|
limit = self.profile('limit_post')
|
|
# If False is returned, no post limit is assumed.
|
|
if limit == 0:
|
|
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()
|
|
|
|
# Posts remaining
|
|
return int(limit) - recent_posts
|
|
|
|
def get_class(self):
|
|
if not self.role:
|
|
return [None, None]
|
|
second = self.role.organization
|
|
first = self.role.image.url
|
|
if self.role.is_static:
|
|
first = settings.STATIC_URL + self.role.image.name
|
|
return [first, second]
|
|
def is_me(self, request):
|
|
if request.user.is_authenticated:
|
|
return (self == request.user)
|
|
else:
|
|
return False
|
|
def has_freedom(self):
|
|
return self.profile('let_freedom')
|
|
# This is the coolest one
|
|
def online_status(self, force=False):
|
|
# Okay so this returns True if the user's online, 2 if they're AFK, False if they're offline and None if they hide it
|
|
if self.hide_online:
|
|
return None
|
|
if (timezone.now() - timedelta(seconds=80)) > self.last_login:
|
|
return False
|
|
elif (timezone.now() - timedelta(seconds=50)) > self.last_login: #
|
|
return 2
|
|
else:
|
|
return True
|
|
def do_avatar(self, feeling=0):
|
|
if self.has_mh and self.avatar:
|
|
feeling = {
|
|
0: 'normal',
|
|
1: 'happy',
|
|
2: 'like',
|
|
3: 'surprised',
|
|
4: 'frustrated',
|
|
5: 'puzzled',
|
|
}.get(feeling, "normal")
|
|
url = '{2}{0}_{1}_face.png'.format(self.avatar, 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
|
|
|
|
def num_yeahs(self):
|
|
return self.yeah_set.count()
|
|
def num_posts(self):
|
|
return self.post_set.count()
|
|
def num_comments(self):
|
|
return self.comment_set.count()
|
|
def num_following(self):
|
|
return self.follow_source.count()
|
|
def num_followers(self):
|
|
return self.follow_target.count()
|
|
def num_friends(self):
|
|
return self.friend_source.count() + self.friend_target.count()
|
|
def can_follow(self, user):
|
|
if UserBlock.find_block(self, user):
|
|
return False
|
|
return True
|
|
def can_view(self, user):
|
|
block = UserBlock.find_block(self, user)
|
|
if block and block.target == user:
|
|
return False
|
|
return True
|
|
def is_following(self, me):
|
|
if not me.is_authenticated:
|
|
return False
|
|
if self == me:
|
|
return True
|
|
#if hasattr(self, 'has_follow'):
|
|
# return self.has_follow
|
|
return self.follow_target.filter(source=me).count() > 0
|
|
def follow(self, source):
|
|
if self.is_following(source) or source == self:
|
|
return False
|
|
if not self.can_follow(source):
|
|
return False
|
|
# Todo: put a follow limit here
|
|
return self.follow_target.create(source=source, target=self)
|
|
def unfollow(self, source):
|
|
if not self.is_following(source) or source == self:
|
|
return False
|
|
return self.follow_target.filter(source=source, target=self).delete()
|
|
def can_block(self, source):
|
|
if self.can_manage() or self.level > source.level or self == source:
|
|
return False
|
|
#if source.profile('moyenne'):
|
|
# return False
|
|
return True
|
|
# BLOCK this user from SOURCE
|
|
def make_block(self, source):
|
|
# trailing
|
|
if UserBlock.objects.filter(source=source, target=self).exists():
|
|
return False
|
|
if not self.can_block(source):
|
|
return False
|
|
fs = Friendship.find_friendship(self, source)
|
|
if fs:
|
|
fs.delete()
|
|
# delete any mutual follows
|
|
Follow.objects.filter(Q(source=self) & Q(target=source) | Q(target=self) & Q(source=source)).delete()
|
|
return UserBlock.objects.create(source=source, target=self)
|
|
def remove_block(self, source):
|
|
find_block = UserBlock.objects.filter(source=source, target=self)
|
|
if not find_block:
|
|
return False
|
|
return find_block.delete()
|
|
def get_posts(self, limit, offset, request, offset_time):
|
|
if request.user.is_authenticated:
|
|
has_yeah = Yeah.objects.filter(post=OuterRef('id'), by=request.user.id)
|
|
posts = self.post_set.select_related('community').select_related('creator').annotate(num_yeahs=Count('yeah', distinct=True), num_comments=Count('comment', distinct=True), yeah_given=Exists(has_yeah, distinct=True)).filter(created__lte=offset_time).order_by('-created')[offset:offset + limit]
|
|
else:
|
|
posts = self.post_set.select_related('community').select_related('creator').annotate(num_yeahs=Count('yeah', distinct=True), num_comments=Count('comment', distinct=True)).filter(created__lte=offset_time).order_by('-created').exclude(community__require_auth=True)[offset:offset + limit]
|
|
if request:
|
|
for post in posts:
|
|
post.setup(request)
|
|
post.recent_comment = post.recent_comment()
|
|
return posts
|
|
def get_comments(self, limit, offset, request, offset_time):
|
|
if request.user.is_authenticated:
|
|
has_yeah = Yeah.objects.filter(comment=OuterRef('id'), by=request.user.id)
|
|
posts = self.comment_set.select_related('original_post').select_related('creator').select_related('original_post__creator').annotate(num_yeahs=Count('yeah', distinct=True), yeah_given=Exists(has_yeah, distinct=True)).filter(created__lte=offset_time).order_by('-created')[offset:offset + limit]
|
|
else:
|
|
posts = self.comment_set.select_related('original_post').select_related('original_post__creator').select_related('creator').annotate(num_yeahs=Count('yeah', distinct=True)).filter(created__lte=offset_time).order_by('-created').exclude(original_post__community__require_auth=True)[offset:offset + limit]
|
|
if request:
|
|
for post in posts:
|
|
post.setup(request)
|
|
return posts
|
|
def get_yeahed(self, type=0, limit=20, offset=0):
|
|
# 0 - post, 1 - comment, 2 - any
|
|
if type == 2:
|
|
yeahs = self.yeah_set.select_related('post').select_related('comment').select_related('comment__original_post').select_related('comment__original_post__creator').annotate(num_yeahs_post=Count('post__yeah', distinct=True), num_yeahs_comment=Count('comment__yeah', distinct=True), num_comments=Count('post__comment', distinct=True)).filter().order_by('-created')[offset:offset + limit]
|
|
else:
|
|
yeahs = self.yeah_set.select_related('post').select_related('post__creator').annotate(num_yeahs_post=Count('post__yeah', distinct=True), num_comments=Count('post__comment', distinct=True)).filter(type=type, post__is_rm=False).order_by('-created')[offset:offset + limit]
|
|
for thing in yeahs:
|
|
if thing.post:
|
|
thing.post.num_yeahs = thing.num_yeahs_post
|
|
thing.post.num_comments = thing.num_comments
|
|
elif thing.comment:
|
|
thing.comment.num_yeahs = thing.num_yeahs_comment
|
|
return yeahs
|
|
def get_following(self, limit=50, offset=0, request=None):
|
|
return self.follow_source.select_related('target').filter().order_by('-created')[offset:offset + limit]
|
|
def get_followers(self, limit=50, offset=0, request=None):
|
|
return self.follow_target.select_related('source').filter().order_by('-created')[offset:offset + limit]
|
|
def notification_count(self):
|
|
return self.notification_to.filter(read=False).count()
|
|
def notification_read(self):
|
|
return self.notification_to.filter(read=False).update(read=True)
|
|
def get_notifications(self):
|
|
return self.notification_to.select_related('context_post').select_related('context_comment').select_related('context_warning').select_related('source').filter().order_by('-latest')[0:64]
|
|
def notifications_clean(self):
|
|
""" Broken - gives OperationError on MySQL
|
|
notif_get = self.notification_to.all().values_list('id', flat=True)
|
|
if notif_get.count() > 64:
|
|
self.notification_to.filter().exclude(id__in=notif_get).delete()
|
|
"""
|
|
|
|
# Admin can-manage
|
|
def can_manage(self):
|
|
if self.level >= settings.level_needed_to_man_users or self.staff:
|
|
can_manage = True
|
|
else:
|
|
can_manage = False
|
|
return can_manage
|
|
# Does self have authority over user?
|
|
def has_authority(self, user):
|
|
if user.is_authenticated:
|
|
if self.staff and not user.staff:
|
|
return True
|
|
if self.level >= user.level:
|
|
return True
|
|
return False
|
|
def friend_state(self, other):
|
|
# Todo: return -1 for cannot, 0 for nothing, 1 for my friend pending, 2 for their friend pending, 3 for friends
|
|
query1 = other.fr_source.filter(target=self, finished=False).exists()
|
|
if query1:
|
|
return 1
|
|
query2 = self.fr_source.filter(target=other, finished=False).exists()
|
|
if query2:
|
|
return 2
|
|
query3 = Friendship.find_friendship(self, other)
|
|
if query3:
|
|
return 3
|
|
return 0
|
|
def get_fr(self, other):
|
|
return FriendRequest.objects.filter(Q(source=self) & Q(target=other) | Q(target=self) & Q(source=other)).exclude(finished=True)
|
|
def get_frs_target(self):
|
|
return FriendRequest.objects.filter(target=self, finished=False).order_by('-created')
|
|
def get_frs_notif(self):
|
|
return FriendRequest.objects.filter(target=self, finished=False, read=False).count()
|
|
def reject_fr(self, target):
|
|
fr = self.get_fr(target)
|
|
if fr:
|
|
try:
|
|
fr.first().finish()
|
|
except:
|
|
pass
|
|
def send_fr(self, source, body=None):
|
|
if self == source or not self.profile().can_friend(source):
|
|
return False
|
|
if self.get_fr(source):
|
|
return False
|
|
if Friendship.find_friendship(self, source):
|
|
return False
|
|
return FriendRequest.objects.create(source=source, target=self, body=body)
|
|
def accept_fr(self, target):
|
|
fr = self.get_fr(target)
|
|
if fr:
|
|
try:
|
|
fr.first().finish()
|
|
except:
|
|
pass
|
|
return Friendship.objects.create(source=self, target=target)
|
|
def cancel_fr(self, target):
|
|
fr = target.get_fr(self)
|
|
if fr:
|
|
try:
|
|
fr.first().finish()
|
|
except:
|
|
pass
|
|
def read_fr(self):
|
|
return self.get_frs_target().update(read=True)
|
|
def delete_friend(self, target):
|
|
fr = Friendship.find_friendship(self, target)
|
|
if fr:
|
|
fr.conversation().all_read()
|
|
fr.delete()
|
|
def get_activity(self, limit=20, offset=0, distinct=False, friends_only=False, request=None):
|
|
#Todo: make distinct work; combine friends and following, but then get posts from them
|
|
friends = Friendship.get_friendships(self, 0)
|
|
friend_ids = []
|
|
for friend in friends:
|
|
friend_ids.append(friend.other(self))
|
|
follows = self.follow_source.filter().values_list('target', flat=True)
|
|
if not friends_only:
|
|
friend_ids.append(self.id)
|
|
for thing in follows:
|
|
friend_ids.append(thing)
|
|
if request.user.is_authenticated:
|
|
has_yeah = Yeah.objects.filter(post=OuterRef('id'), by=request.user.id)
|
|
if distinct:
|
|
posts = Post.objects.select_related('creator').select_related('community').annotate(num_yeahs=Count('yeah', distinct=True), num_comments=Count('comment', distinct=True), yeah_given=Exists(has_yeah, distinct=True)).annotate(max_created=Max('creator__post__created')).filter(created=F('max_created')).filter(creator__in=friend_ids).order_by('-created')[offset:offset + limit]
|
|
else:
|
|
posts = Post.objects.select_related('creator').select_related('community').annotate(num_yeahs=Count('yeah', distinct=True), num_comments=Count('comment', distinct=True), yeah_given=Exists(has_yeah, distinct=True)).filter(creator__in=friend_ids).order_by('-created')[offset:offset + limit]
|
|
if request:
|
|
for post in posts:
|
|
post.setup(request)
|
|
post.recent_comment = post.recent_comment()
|
|
return posts
|
|
def community_favorites(self, all=False):
|
|
if not all:
|
|
favorites = self.communityfavorite_set.order_by('-created').filter(community__is_rm=False)[:8]
|
|
else:
|
|
favorites = self.communityfavorite_set.order_by('-created').filter(community__is_rm=False)
|
|
communities = []
|
|
for fav in favorites:
|
|
communities.append(fav.community)
|
|
del(favorites)
|
|
return communities
|
|
def wake(self, addr=None):
|
|
if addr and not addr == self.addr:
|
|
self.addr = addr
|
|
return self.save(update_fields=['addr', 'last_login'])
|
|
return self.save(update_fields=['last_login'])
|
|
|
|
def has_postspam(self, body, screenshot=None, drawing=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 == body:
|
|
return True
|
|
return False
|
|
|
|
def get_latest_msg(self, me):
|
|
conversation = Conversation.objects.filter(Q(source=self) & Q(target=me) | Q(target=self) & Q(source=me)).order_by('-created')[:1].first()
|
|
if not conversation:
|
|
return False
|
|
return conversation.latest_message(me)
|
|
def conversations(self):
|
|
return Conversation.objects.filter(Q(source=self) | Q(target=self)).order_by('-created')
|
|
def msg_count(self):
|
|
# Gets messages with conversations I am involved in, then looks for those unread and not by me, gets count and returns it
|
|
messages = Message.objects.filter(Q(conversation__source=self.id) | Q(conversation__target=self.id)).filter(read=False).exclude(creator=self.id).count()
|
|
return messages
|
|
def password_reset_email(self, request):
|
|
htmlmsg = render_to_string('closedverse_main/help/email.html', {
|
|
'menulogo': request.build_absolute_uri(brand_logo),
|
|
'contact': request.build_absolute_uri(reverse('main:help-contact')),
|
|
'link': request.build_absolute_uri(reverse('main:forgot-passwd')) + "?token=" + base64.urlsafe_b64encode(bytes(self.password, 'utf-8')).decode(),
|
|
})
|
|
subj = '{1} password reset for "{0}"'.format(self.username, brand_name)
|
|
return send_mail(
|
|
subject=subj,
|
|
message='',
|
|
html_message=htmlmsg,
|
|
from_email="{1} <{0}>".format(settings.DEFAULT_FROM_EMAIL, brand_name),
|
|
recipient_list=[self.email],
|
|
fail_silently=False)
|
|
def find_related(self):
|
|
return User.objects.filter(id__in=LoginAttempt.objects.filter(Q(addr=self.addr), Q(user=self.id)).values_list('user', flat=True)).exclude(id=self.id)
|
|
@staticmethod
|
|
def search(query='', limit=50, offset=0, request=None):
|
|
return User.objects.filter(Q(username__icontains=query) | Q(nickname__icontains=query)).order_by('-created')[offset:offset + limit]
|
|
@staticmethod
|
|
def email_in_use(addr, request=None):
|
|
if not addr:
|
|
return False
|
|
if request:
|
|
return User.objects.filter(email__iexact=addr).exclude(id=request.user.id).exists()
|
|
else:
|
|
return User.objects.filter(email__iexact=addr).exists()
|
|
@staticmethod
|
|
def nnid_in_use(id, request=None):
|
|
if not id:
|
|
return False
|
|
if request:
|
|
nnid_real = id.lower().replace('-', '').replace('.', '')
|
|
return Profile.objects.filter(origin_id__iexact=nnid_real).exclude(user__id=request.user.id).exists()
|
|
else:
|
|
return Profile.objects.filter(origin_id=id).exists()
|
|
@staticmethod
|
|
def get_from_passwd(passwd):
|
|
try:
|
|
user = User.objects.get(password=base64.urlsafe_b64decode(passwd).decode())
|
|
# Too lazy to make except cases
|
|
except:
|
|
return False
|
|
return user
|
|
|
|
class Ban(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
by = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE, related_name='banned_by')
|
|
to = models.ForeignKey(User, on_delete=models.CASCADE, related_name='banned_user')
|
|
reason = models.TextField(null=True, blank=True)
|
|
expiry_date = models.DateTimeField(help_text='The date and time on which this ban will expire, Set this way off into the future if this ban should be permanent')
|
|
active = models.BooleanField(default=True, help_text='Untick this to disable this ban')
|
|
ip_address = models.CharField(null=True, blank=True, max_length=256, help_text='Put an IP address in here to make this an IP ban. When using the ban function outside of the Django admin panel, this is filled in automatically.')
|
|
|
|
def is_expired(self):
|
|
if timezone.now() > self.expiry_date:
|
|
return True
|
|
return False
|
|
def __str__(self):
|
|
return "Ban for " + str(self.to)
|
|
|
|
# The new warning system, Warnings do not contribute to bans, they just show as warnings.
|
|
# Can also be used as a means of reporting incident history.
|
|
class Warning(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='Warned_by', help_text="This is not shown to the recipient")
|
|
to = models.ForeignKey(User, on_delete=models.CASCADE, related_name='Warned_user')
|
|
reason = models.TextField(null=True, blank=True)
|
|
|
|
def save(self, *args, **kwargs):
|
|
# check if the object is being created or updated
|
|
is_new = self.pk is None
|
|
super().save(*args, **kwargs)
|
|
# create new notification only after new warning is created
|
|
if is_new:
|
|
Notification.give_notification(user=self.by, type=5, to=self.to, warning=self)
|
|
|
|
def __str__(self):
|
|
return "Warning for " + str(self.to)
|
|
|
|
# An invite system, for closed off communities or for whatever reason.
|
|
class Invites(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
creator = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE, related_name='invite_creator')
|
|
code = models.CharField(max_length=36, default=uuid.uuid4)
|
|
used = models.BooleanField(default=False)
|
|
void = models.BooleanField(default=False)
|
|
used_by = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE, related_name='invited_user')
|
|
|
|
def is_valid(self):
|
|
if self.used or self.void:
|
|
return False
|
|
if not self.creator.can_invite:
|
|
return False
|
|
return True
|
|
|
|
def __str__(self):
|
|
return "invite by " + str(self.creator)
|
|
|
|
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)
|
|
# 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, 'both'), (5, 'PC'), (6, 'Xbox'), (7, 'Playstation')))
|
|
tags = models.CharField(blank=True, help_text='Provides special functionality for specific communities.', null=True, max_length=255, choices=(('announcements', 'main announcement community'), ('changelog', 'main changelog'), ('activity', 'Activity Feed posting community'), ('general', 'General Discussion Community')))
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
updated = models.DateTimeField(auto_now=True)
|
|
is_rm = models.BooleanField(default=False, help_text='Tick this on to remove the community.')
|
|
is_feature = models.BooleanField(default=False, help_text='Feature this community on the front page where everyone can see it.')
|
|
require_auth = models.BooleanField(default=False, help_text='Force your users to sign in to view this community.')
|
|
rank_needed_to_post = models.IntegerField(default=0, help_text='Make admin only communities.')
|
|
creator = models.ForeignKey(User, blank=True, null=True, help_text='Who owns this community? It\'s highly recommended to not fill this on for general communities. Keep in mind that owners can block others from using communities they own.', on_delete=models.CASCADE)
|
|
|
|
objects = PostManager()
|
|
real = models.Manager()
|
|
def popularity(self):
|
|
if self.creator:
|
|
# don't count posts from the community owner.
|
|
popularity = Post.objects.filter(community=self).exclude(creator=self.creator).count()
|
|
else:
|
|
popularity = Post.objects.filter(community=self).count()
|
|
return popularity
|
|
def __str__(self):
|
|
return self.name
|
|
def icon(self):
|
|
if self.ico:
|
|
return self.ico.url
|
|
else:
|
|
return settings.STATIC_URL + "img/title-icon-default.png"
|
|
def type_txt(self):
|
|
if self.type == 1:
|
|
return {
|
|
0: "",
|
|
1: "3DS Game",
|
|
2: "Wii U Game",
|
|
3: "Switch Game",
|
|
4: "Wii U / 3DS Game",
|
|
5: 'PC Game',
|
|
6: 'Xbox Game',
|
|
7: 'Playstation Game',
|
|
}.get(self.platform)
|
|
else:
|
|
return {
|
|
0: "General community",
|
|
1: "Game community",
|
|
2: "Special community",
|
|
3: "User owned community",
|
|
}.get(self.type)
|
|
def type_platform(self):
|
|
thing = {
|
|
0: "",
|
|
1: "3ds",
|
|
2: "wiiu",
|
|
3: "switch",
|
|
4: "wiiu-3ds",
|
|
5: 'pc',
|
|
6: 'xbox',
|
|
7: 'ps',
|
|
8: 'youre-mom',
|
|
}.get(self.platform)
|
|
if not thing:
|
|
return None
|
|
return "img/platform-tag-" + thing + ".png"
|
|
def is_activity(self):
|
|
return self.tags == 'activity'
|
|
def clickable(self):
|
|
return not self.is_activity() and not self.type == 4
|
|
# have yet to see a usage of this without all of these params so sure, offset_time is default
|
|
#def get_posts(self, limit=50, offset=0, request=None, favorite=False):
|
|
def get_posts(self, limit, offset, request, offset_time):
|
|
if request.user.is_authenticated:
|
|
# get users who blocked you
|
|
blocked_me = request.user.block_target.filter().values('source')
|
|
|
|
has_yeah = Yeah.objects.filter(post=OuterRef('id'), by=request.user.id)
|
|
posts = Post.objects.select_related('creator').annotate(num_yeahs=Count('yeah', distinct=True), num_comments=Count('comment', distinct=True), yeah_given=Exists(has_yeah, distinct=True)
|
|
).exclude(creator__id__in=Subquery(blocked_me)).filter(community_id=self.id, created__lte=offset_time).order_by('-created')[offset:offset + limit]
|
|
else:
|
|
posts = Post.objects.select_related('creator').annotate(num_yeahs=Count('yeah', distinct=True), num_comments=Count('comment', distinct=True)).filter(community_id=self.id, created__lte=offset_time).order_by('-created').exclude(community__require_auth=True)[offset:offset + limit]
|
|
if request:
|
|
for post in posts:
|
|
post.setup(request)
|
|
# THE TRUE METHOD
|
|
if request.user.is_authenticated:
|
|
post.user_is_blocked = UserBlock.find_block(post.creator, request.user)
|
|
#print(str(post) + ' to ' + str(self.creator) + ':' + str(post.user_is_blocked))
|
|
post.recent_comment = post.recent_comment()
|
|
return posts
|
|
|
|
def Community_block(self, request):
|
|
# This goes both ways.
|
|
if request.user.is_authenticated and not request.user.can_manage():
|
|
if UserBlock.find_block(self.creator, request.user):
|
|
return True
|
|
return False
|
|
|
|
def post_perm(self, request):
|
|
if not request.user.active:
|
|
return False
|
|
if self.Community_block(request):
|
|
return False
|
|
if request.user.level >= self.rank_needed_to_post:
|
|
return True
|
|
elif request.user.staff == True:
|
|
return True
|
|
# If the community is made by you, you should be able to post in there regardless.
|
|
elif request.user == self.creator:
|
|
return True
|
|
else:
|
|
return False
|
|
def can_edit_community(self, request):
|
|
# yanderedev moment
|
|
if not request.user.is_authenticated:
|
|
return False
|
|
# If the user is a mod but can't post in one community, the user should not edit it either.
|
|
if not request.user.level >= self.rank_needed_to_post and not request.user.staff == True:
|
|
return False
|
|
|
|
if request.user == self.creator:
|
|
return True
|
|
elif request.user.level >= settings.level_needed_to_man_communities or request.user.staff == True:
|
|
return True
|
|
else:
|
|
return False
|
|
def has_favorite(self, request):
|
|
if request.user.communityfavorite_set.filter(community=self).exists():
|
|
return True
|
|
return False
|
|
def favorite_add(self, request):
|
|
if not self.has_favorite(request):
|
|
return request.user.communityfavorite_set.create(community=self)
|
|
def favorite_rm(self, request):
|
|
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', 'hitler']:
|
|
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]
|
|
|
|
def get_all(type=0, offset=0, limit=12):
|
|
return Community.objects.filter(type=type).order_by('-created')[offset:offset + limit]
|
|
|
|
class Meta:
|
|
verbose_name_plural = "communities"
|
|
|
|
class CommunityFavorite(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
by = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
community = models.ForeignKey(Community, on_delete=models.CASCADE)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "Community favorite by " + str(self.by) + " for " + str(self.community)
|
|
|
|
class Post(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
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='')
|
|
url = models.URLField(max_length=1200, null=True, blank=True, default='')
|
|
spoils = models.BooleanField(default=False)
|
|
disable_yeah = models.BooleanField(default=False)
|
|
lock_comments = models.SmallIntegerField(default=0, choices=((0, 'Not locked'), (1, 'Locked by user'), (2, 'Locked by mod')), help_text='People will not be able to comment on posts that are locked.')
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
edited = models.DateTimeField(auto_now=True)
|
|
befores = models.TextField(null=True, blank=True, help_text='If this post gets edited, the old body will be here.')
|
|
poll = models.ForeignKey('Poll', null=True, blank=True, on_delete=models.CASCADE, help_text='A busted ass poll feature that does not work.')
|
|
has_edit = models.BooleanField(default=False)
|
|
is_rm = models.BooleanField(default=False, help_text='Tick this to hide this post')
|
|
status = models.SmallIntegerField(default=0, choices=post_status, help_text='Used to distinguish how a post was removed. Our purging feature uses this to determine what posts to restore and what not to.')
|
|
creator = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
objects = PostManager()
|
|
real = models.Manager()
|
|
|
|
def __str__(self):
|
|
return self.body[:250]
|
|
def is_reply(self):
|
|
return False
|
|
def trun(self):
|
|
if self.is_rm:
|
|
return 'deleted'
|
|
if self.drawing:
|
|
return 'drawing'
|
|
else:
|
|
return self.body
|
|
def yt_vid(self):
|
|
try:
|
|
thing = re.search('(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})', self.url).group(6)
|
|
except:
|
|
return False
|
|
return thing
|
|
def discord_vid(self):
|
|
try:
|
|
thing = re.search('(https?://)?(www\.)?(cdn)?(discordapp)\.(com)/attachments/([a-zA-Z0-9]+)/([a-zA-Z0-9]+)/(.+)', self.url).group(8)
|
|
if thing.endswith(".mp4") or thing.endswith(".webm"):
|
|
return thing
|
|
else:
|
|
return False
|
|
except:
|
|
return False
|
|
def has_line_trun(self):
|
|
if self.body and len(self.body.splitlines()) > 10:
|
|
return True
|
|
return False
|
|
def is_mine(self, user):
|
|
if user.is_authenticated:
|
|
return (self.creator == user)
|
|
else:
|
|
return False
|
|
#def yeah_notification(self, request):
|
|
# ???? What is this
|
|
#Notification.give_notification
|
|
def number_yeahs(self):
|
|
if hasattr(self, 'num_yeahs'):
|
|
return self.num_yeahs
|
|
return self.yeah_set.count()
|
|
def has_yeah(self, request):
|
|
if request.user.is_authenticated:
|
|
if hasattr(self, 'yeah_given'):
|
|
return self.yeah_given
|
|
else:
|
|
return self.yeah_set.filter(by=request.user).exists()
|
|
else:
|
|
return False
|
|
def can_yeah(self, request):
|
|
if not request.user.is_authenticated:
|
|
return False
|
|
if not request.user.is_active():
|
|
return False
|
|
if self.community.Community_block(request):
|
|
return False
|
|
if self.is_mine(request.user) or UserBlock.find_block(self.creator, request.user):
|
|
return False
|
|
return True
|
|
def can_rm(self, request):
|
|
if self.creator.has_authority(request.user):
|
|
return False
|
|
return True
|
|
def give_yeah(self, request):
|
|
if not request.user.has_freedom() and Yeah.objects.filter(by=request.user, created__gt=timezone.now() - timedelta(seconds=5)).exists():
|
|
return False
|
|
if self.has_yeah(request):
|
|
return True
|
|
if not self.can_yeah(request):
|
|
return False
|
|
return self.yeah_set.create(by=request.user, post=self)
|
|
def remove_yeah(self, request):
|
|
if not self.has_yeah(request):
|
|
return True
|
|
return self.yeah_set.filter(by=request.user).delete()
|
|
def number_comments(self):
|
|
# Number of comments cannot be accurate due to comment deleting
|
|
#if hasattr(self, 'num_comments'):
|
|
# return self.num_comments
|
|
return self.comment_set.count()
|
|
def get_yeahs(self, request):
|
|
return self.yeah_set.order_by('-created')[0:30]
|
|
def can_comment(self, request):
|
|
if self.number_comments() > 500:
|
|
return False
|
|
if not request.user.active:
|
|
return False
|
|
# yeah this is fucking nuts. It's basically a ban from an entire community.
|
|
if self.community.Community_block(request):
|
|
return False
|
|
if self.lock_comments != 0:
|
|
return False
|
|
if UserBlock.find_block(self.creator, request.user):
|
|
return False
|
|
return True
|
|
|
|
def can_lock_comments(self, request):
|
|
if self.lock_comments != 0:
|
|
return False
|
|
# If you are a mod, you can bypass the timer
|
|
# The timer is a personal choice of mine, I don't want users to pussy out of a fight too early or whatever.
|
|
# Always annoys me when someone has a dumb ass take only for them to turn off the comments immediately.
|
|
if self.created < timezone.now() - timedelta(hours=2) or request.user.can_manage():
|
|
if self.creator == request.user:
|
|
return True
|
|
if not self.creator.has_authority(request.user) and request.user.can_manage():
|
|
return True
|
|
return False
|
|
|
|
def lock_the_comments_up(self, request):
|
|
if request and self.can_lock_comments(request):
|
|
if self.is_mine(request.user):
|
|
self.lock_comments = 1
|
|
else:
|
|
self.lock_comments = 2
|
|
AuditLog.objects.create(type=3, post=self, user=self.creator, by=request.user)
|
|
self.save()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def get_comments(self, request=None, limit=0, offset=0):
|
|
if request.user.is_authenticated:
|
|
blocked_me = request.user.block_target.filter().values('source')
|
|
|
|
has_yeah = Yeah.objects.filter(comment=OuterRef('id'), by=request.user.id)
|
|
comments_pre = self.comment_set.select_related('creator').annotate(num_yeahs=Count('yeah'), yeah_given=Exists(has_yeah)
|
|
).exclude(creator__id__in=Subquery(blocked_me)).filter(original_post=self).order_by('created')
|
|
comments = comments_pre
|
|
if limit:
|
|
comments = comments_pre[offset:offset + limit]
|
|
elif offset:
|
|
comments = comments_pre[offset:]
|
|
else:
|
|
comments_pre = self.comment_set.select_related('creator').annotate(num_yeahs=Count('yeah')).filter(original_post=self).order_by('created').exclude(original_post__community__require_auth=True)
|
|
comments = comments_pre
|
|
if limit:
|
|
comments = comments_pre[offset:offset + limit]
|
|
elif offset:
|
|
comments = comments_pre[offset:]
|
|
if request:
|
|
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
|
|
comments = self.comment_set.filter(spoils=False).exclude(creator=self.creator).order_by('-created')[:1]
|
|
return comments.first()
|
|
def change(self, request):
|
|
if not self.is_mine(request.user) or self.has_edit:
|
|
return 1
|
|
if len(request.POST['body']) > 2200 or len(request.POST['body']) < 1:
|
|
return 1
|
|
if not self.befores:
|
|
befores_json = []
|
|
else:
|
|
befores_json = json.loads(self.befores)
|
|
befores_json.append(self.body)
|
|
self.befores = json.dumps(befores_json)
|
|
self.body = request.POST['body']
|
|
self.spoils = request.POST.get('is_spoiler', False)
|
|
self.feeling = request.POST.get('feeling_id', 0)
|
|
if not timezone.now() < self.created + timezone.timedelta(minutes=2):
|
|
self.has_edit = True
|
|
return self.save()
|
|
def is_favorite(self, user):
|
|
profile = user.profile()
|
|
if profile.favorite == self:
|
|
return True
|
|
else:
|
|
return False
|
|
def favorite(self, user):
|
|
if not self.is_mine(user):
|
|
return False
|
|
profile = user.profile()
|
|
if profile.favorite == self:
|
|
return False
|
|
profile.favorite = self
|
|
return profile.save()
|
|
def unfavorite(self, user):
|
|
if not self.is_mine(user):
|
|
return False
|
|
profile = user.profile()
|
|
if profile.favorite == self:
|
|
profile.favorite = None
|
|
return profile.save()
|
|
def rm(self, request):
|
|
if request and not self.is_mine(request.user) and not self.can_rm(request):
|
|
return False
|
|
if self.is_favorite(self.creator):
|
|
self.unfavorite(self.creator)
|
|
self.is_rm = True
|
|
if self.is_mine(request.user):
|
|
self.status = 1
|
|
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)
|
|
self.can_yeah = self.can_yeah(request)
|
|
self.is_mine = self.is_mine(request.user)
|
|
def max_yeahs():
|
|
try:
|
|
max_yeahs_post = Post.objects.annotate(num_yeahs=Count('yeah')).aggregate(max_yeahs=Max('num_yeahs'))['max_yeahs']
|
|
except:
|
|
return None
|
|
the_post = Post.objects.annotate(num_yeahs=Count('yeah')).filter(num_yeahs=max_yeahs_post).order_by('-created')
|
|
return the_post.first()
|
|
|
|
class Comment(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
original_post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
|
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)
|
|
spoils = models.BooleanField(default=False)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
edited = models.DateTimeField(auto_now=True)
|
|
befores = models.TextField(null=True, blank=True)
|
|
has_edit = models.BooleanField(default=False)
|
|
is_rm = models.BooleanField(default=False, help_text='Tick this to hide this comment')
|
|
status = models.SmallIntegerField(default=0, choices=post_status, help_text='Used to distinguish how a comment was removed. Our purging feature uses this to determine what comments to restore and what not to.')
|
|
creator = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
|
|
|
|
objects = PostManager()
|
|
real = models.Manager()
|
|
|
|
def __str__(self):
|
|
return self.body[:250]
|
|
def is_reply(self):
|
|
return True
|
|
def trun(self):
|
|
if self.is_rm:
|
|
return 'deleted'
|
|
if self.drawing:
|
|
return '(drawing)'
|
|
else:
|
|
return self.body
|
|
def is_mine(self, user):
|
|
if user.is_authenticated:
|
|
return (self.creator == user)
|
|
else:
|
|
return False
|
|
def number_yeahs(self):
|
|
if hasattr(self, 'num_yeahs'):
|
|
return self.num_yeahs
|
|
return self.yeah_set.count()
|
|
def has_yeah(self, request):
|
|
if request.user.is_authenticated:
|
|
if hasattr(self, 'yeah_given'):
|
|
return self.yeah_given
|
|
else:
|
|
return self.yeah_set.filter(by=request.user).exists()
|
|
else:
|
|
return False
|
|
def can_yeah(self, request):
|
|
if not request.user.is_authenticated or not request.user.is_active():
|
|
return False
|
|
if self.is_mine(request.user) or UserBlock.find_block(self.creator, request.user):
|
|
return False
|
|
return True
|
|
def can_rm(self, request):
|
|
# if the creator of the post does not have authority, you can remove it.
|
|
if not self.creator.has_authority(request.user):
|
|
return True
|
|
return False
|
|
def give_yeah(self, request):
|
|
if not request.user.has_freedom() and Yeah.objects.filter(by=request.user, created__gt=timezone.now() - timedelta(seconds=5)).exists():
|
|
return False
|
|
if self.has_yeah(request):
|
|
return True
|
|
if not self.can_yeah(request):
|
|
return False
|
|
return self.yeah_set.create(by=request.user, type=1, comment=self)
|
|
def remove_yeah(self, request):
|
|
if not self.has_yeah(request):
|
|
return True
|
|
return self.yeah_set.filter(by=request.user).delete()
|
|
def get_yeahs(self, request):
|
|
return Yeah.objects.filter(type=1, comment=self).order_by('-created')[0:30]
|
|
def owner_post(self):
|
|
return (self.creator == self.original_post.creator)
|
|
def change(self, request):
|
|
if not self.is_mine(request.user) or self.has_edit:
|
|
return 1
|
|
if len(request.POST['body']) > 2200 or len(request.POST['body']) < 1:
|
|
return 1
|
|
if not self.befores:
|
|
befores_json = []
|
|
else:
|
|
befores_json = json.loads(self.befores)
|
|
befores_json.append(self.body)
|
|
self.befores = json.dumps(befores_json)
|
|
self.body = request.POST['body']
|
|
self.spoils = request.POST.get('is_spoiler', False)
|
|
self.feeling = request.POST.get('feeling_id', 0)
|
|
if not timezone.now() < self.created + timezone.timedelta(minutes=2):
|
|
self.has_edit = True
|
|
return self.save()
|
|
def rm(self, request):
|
|
if request and not self.is_mine(request.user) and not self.can_rm(request):
|
|
return False
|
|
self.is_rm = True
|
|
if self.is_mine(request.user):
|
|
self.status = 1
|
|
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)
|
|
self.can_yeah = self.can_yeah(request)
|
|
self.is_mine = self.is_mine(request.user)
|
|
|
|
class Yeah(models.Model):
|
|
# 2023-08-16: tried to remove this but it is the primary key so it's kind of hard to change
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
|
|
by = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
type = models.SmallIntegerField(default=0, choices=((0, 'post'), (1, 'comment'), ))
|
|
post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
|
|
# kldsjfldsfsdfd
|
|
#spam = models.BooleanField(default=False)
|
|
comment = models.ForeignKey(Comment, null=True, blank=True, on_delete=models.CASCADE)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
a = "from " + self.by.username + " to "
|
|
if self.post:
|
|
a += "post " + str(self.post.id)
|
|
elif self.comment:
|
|
a += "comment " + str(self.comment.id)
|
|
return a
|
|
|
|
class Profile(models.Model):
|
|
is_new = models.BooleanField(default=True)
|
|
id = models.AutoField(primary_key=True)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
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='')
|
|
#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")
|
|
))
|
|
|
|
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='')
|
|
favorite = models.ForeignKey(Post, blank=True, null=True, on_delete=models.SET_NULL)
|
|
|
|
let_yeahnotifs = models.BooleanField(default=True)
|
|
let_freedom = models.BooleanField(default=True, help_text='Restrict this user from posting images, videos, URLs, and even making new accounts.')
|
|
# Todo: When you see this, implement it; make it a bool that determines whether the user should be able to edit their avatar; if this is true and
|
|
#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
|
|
def origin_id_public(self, user=None):
|
|
if user == self.user:
|
|
return self.origin_id
|
|
if self.id_visibility == 2:
|
|
return 1
|
|
elif self.id_visibility == 1:
|
|
if not user.is_authenticated or not Friendship.find_friendship(self.user, user):
|
|
return 1
|
|
return self.origin_id
|
|
elif not self.origin_id:
|
|
return None
|
|
return self.origin_id
|
|
def yeahs_visible(self, user=None):
|
|
if user == self.user:
|
|
return True
|
|
if self.yeahs_visibility == 2:
|
|
return False
|
|
elif self.yeahs_visibility == 1:
|
|
if not user.is_authenticated or not Friendship.find_friendship(self.user, user):
|
|
return False
|
|
return True
|
|
return True
|
|
def comments_visible(self, user=None):
|
|
if user == self.user:
|
|
return True
|
|
if self.comments_visibility == 2:
|
|
return False
|
|
elif self.comments_visibility == 1:
|
|
if not user.is_authenticated or not Friendship.find_friendship(self.user, user):
|
|
return False
|
|
return True
|
|
return True
|
|
def can_friend(self, user=None):
|
|
if self.let_friendrequest == 2:
|
|
return False
|
|
elif self.let_friendrequest == 1:
|
|
if not user.is_following(self.user):
|
|
return False
|
|
return True
|
|
if user.is_authenticated and UserBlock.find_block(self.user, user):
|
|
return False
|
|
return True
|
|
def got_fullurl(self):
|
|
if self.weblink:
|
|
try:
|
|
URLValidator()(value=self.weblink)
|
|
except ValidationError:
|
|
return False
|
|
return True
|
|
return False
|
|
def setup(self, request):
|
|
self.origin_id_public = self.origin_id_public(request.user)
|
|
self.yeahs_visible = self.yeahs_visible(request.user)
|
|
self.comments_visible = self.comments_visible(request.user)
|
|
if request.user.is_authenticated and request.user != self.user:
|
|
# these aren't on the user object so arguably these should not be here
|
|
# but at this point i do not care just throw away this whole codebase please
|
|
self.can_follow = self.user.can_follow(request.user)
|
|
self.can_block = self.user.can_block(request.user)
|
|
# we will use hasattr
|
|
if UserBlock.objects.filter(source=request.user, target=self.user).exists():
|
|
self.is_blocked = True
|
|
|
|
class Follow(models.Model):
|
|
# Todo: remove this
|
|
id = models.AutoField(primary_key=True)
|
|
source = models.ForeignKey(User, related_name='follow_source', on_delete=models.CASCADE)
|
|
target = models.ForeignKey(User, related_name='follow_target', on_delete=models.CASCADE)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "follow: from " + self.source.username + " to " + self.target.username
|
|
|
|
class Notification(models.Model):
|
|
# Todo: make this a plain int at some point
|
|
unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
|
|
|
to = models.ForeignKey(User, related_name='notification_to', on_delete=models.CASCADE)
|
|
source = models.ForeignKey(User, related_name='notification_sender', on_delete=models.CASCADE)
|
|
read = models.BooleanField(default=False)
|
|
type = models.SmallIntegerField(choices=(
|
|
(0, 'Yeah on post'),
|
|
(1, 'Yeah on comment'),
|
|
(2, 'Comment on my post'),
|
|
(3, 'Comment on others\' post'),
|
|
(4, 'Follow to me'),
|
|
(5, 'Warning received'),
|
|
))
|
|
merges = models.TextField(blank=True, default='')
|
|
context_post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
|
|
context_comment = models.ForeignKey(Comment, null=True, blank=True, on_delete=models.CASCADE)
|
|
context_warning = models.ForeignKey(Warning, null=True, blank=True, on_delete=models.CASCADE)
|
|
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
latest = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return "Notification from " + str(self.source) + " to " + str(self.to) + " with type \"" + self.get_type_display() + "\""
|
|
def url(self):
|
|
if self.type == 5:
|
|
return None
|
|
what_type = {
|
|
0: 'main:post-view',
|
|
1: 'main:comment-view',
|
|
2: 'main:post-view',
|
|
3: 'main:post-view',
|
|
4: 'main:user-followers',
|
|
}.get(self.type)
|
|
if self.type == 0 or self.type == 2 or self.type == 3:
|
|
what_id = self.context_post_id
|
|
elif self.type == 1:
|
|
what_id = self.context_comment_id
|
|
elif self.type == 4:
|
|
what_id = self.to.username
|
|
return reverse(what_type, args=[what_id])
|
|
def merge(self, user):
|
|
self.latest = timezone.now()
|
|
self.read = False
|
|
if not self.merges:
|
|
u = []
|
|
else:
|
|
u = json.loads(self.merges)
|
|
u.append(user.id)
|
|
self.merges = json.dumps(u)
|
|
self.save()
|
|
#return self.merged.create(source=user, to=self.to, type=self.type, context_post=self.context_post, context_comment=self.context_comment)
|
|
def set_unread(self):
|
|
self.read = False
|
|
self.latest = timezone.now()
|
|
return self.save()
|
|
def all_users(self):
|
|
if not self.merges:
|
|
u = []
|
|
else:
|
|
u = organ(json.loads(self.merges))
|
|
arr = []
|
|
arr.append(self.source)
|
|
for user in u:
|
|
# Todo: Clean this up and make this block better
|
|
if not u in arr:
|
|
try:
|
|
arr.append(User.objects.get(id=user))
|
|
except:
|
|
pass
|
|
del(u)
|
|
return arr
|
|
"""
|
|
arr = []
|
|
arr.append(self.source)
|
|
merges = self.merged.filter().order_by('created')
|
|
for merge in merges:
|
|
arr.append(merge.source)
|
|
return arr
|
|
"""
|
|
def setup(self, user):
|
|
# Only have is_following if the type is a follow; save SQL queries
|
|
if self.type == 4:
|
|
self.source.is_following = self.source.is_following(user)
|
|
else:
|
|
self.source.is_following = False
|
|
# In the future, please put giving notifications for classes into their respective classes (right now they're in views)
|
|
@staticmethod
|
|
def give_notification(user, type, to, post=None, comment=None, warning=None):
|
|
# Just keeping this simple for now, might want to make it better later
|
|
# If the user sent a notification to this user at least 5 seconds ago, return False
|
|
# Or if user is to
|
|
# Or if yeah notifications are off and this is a yeah notification
|
|
if type == 5:
|
|
return user.notification_sender.create(type=type, to=to, context_warning=warning)
|
|
user_is_self_unk = (not type == 3 and user == to)
|
|
is_notification_too_fast = (user.notification_sender.filter(created__gt=timezone.now() - timedelta(seconds=5), type=type).exclude(type=4) and not type == 3)
|
|
user_no_yeahnotif = (not to.let_yeahnotifs() and (type == 0 or type == 1))
|
|
if user_is_self_unk or is_notification_too_fast or user_no_yeahnotif:
|
|
return False
|
|
# Search for my own notifiaction. If it exists, set it as unread.
|
|
merge_own = user.notification_sender.filter(created__gt=timezone.now() - timedelta(hours=8), to=to, type=type, context_post=post, context_comment=comment)
|
|
if merge_own:
|
|
# If it's merged, don't unread that one, but unread what it's merging.
|
|
return merge_own.first().set_unread()
|
|
# Search for a notification already there so we can merge with it if it exists
|
|
merge_s = Notification.objects.filter(created__gt=timezone.now() - timedelta(hours=8), to=to, type=type, context_post=post, context_comment=comment)
|
|
# If it exists, merge with it. Else, create a new notification.
|
|
if merge_s:
|
|
return merge_s.first().merge(user)
|
|
else:
|
|
return user.notification_sender.create(source=user, type=type, to=to, context_post=post, context_comment=comment)
|
|
|
|
class Complaint(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
creator = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
type = models.SmallIntegerField(choices=(
|
|
(0, 'Bug report'),
|
|
(1, 'Suggestion'),
|
|
(2, 'Want'),
|
|
))
|
|
body = models.TextField(blank=True, default='')
|
|
sex = models.SmallIntegerField(null=True, choices=((0, 'girl'), (1, 'privileged one'), (2, '(none)'),
|
|
))
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "\"" + str(self.body) + "\" from " + str(self.creator) + " as a " + str(self.get_sex_display())
|
|
def has_past_sent(user):
|
|
return user.complaint_set.filter(created__gt=timezone.now() - timedelta(minutes=5)).exists()
|
|
|
|
class FriendRequest(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
source = models.ForeignKey(User, related_name='fr_source', on_delete=models.CASCADE)
|
|
target = models.ForeignKey(User, related_name='fr_target', on_delete=models.CASCADE)
|
|
body = models.TextField(blank=True, null=True, default='')
|
|
read = models.BooleanField(default=False)
|
|
finished = models.BooleanField(default=False)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "friend request ("+str(self.finished)+"): from " + str(self.source) + " to " + str(self.target)
|
|
def finish(self):
|
|
self.finished = True
|
|
self.save()
|
|
|
|
class Friendship(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
source = models.ForeignKey(User, related_name='friend_source', on_delete=models.CASCADE)
|
|
target = models.ForeignKey(User, related_name='friend_target', on_delete=models.CASCADE)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
latest = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return "friendship with " + str(self.source) + " and " + str(self.target)
|
|
def update(self):
|
|
return self.save(update_fields=['latest'])
|
|
def other(self, user):
|
|
if self.source == user:
|
|
return self.target
|
|
return self.source
|
|
def conversation(self):
|
|
conv = Conversation.objects.filter(Q(source=self.source) & Q(target=self.target) | Q(target=self.source) & Q(source=self.target)).order_by('-created')
|
|
if not conv:
|
|
return Conversation.objects.create(source=self.source, target=self.target)
|
|
return conv.first()
|
|
@staticmethod
|
|
def get_friendships(user, limit=50, offset=0, latest=False, online_only=False):
|
|
if not limit:
|
|
return Friendship.objects.filter(Q(source=user) | Q(target=user)).order_by('-created')
|
|
if latest:
|
|
if online_only:
|
|
delta = timezone.now() - timedelta(seconds=48)
|
|
awman = []
|
|
for friend in Friendship.objects.filter(Q(source=user) | Q(target=user)).order_by('-latest')[offset:offset + limit]:
|
|
if friend.other(user).last_login > delta:
|
|
awman.append(friend)
|
|
return awman
|
|
# Fix all of this at some point
|
|
# return Friendship.objects.filter(
|
|
#source=When(source__ne=user, source__last_login__gt=delta),
|
|
#target=When(target__ne=user, target__last_login__gt=delta)
|
|
#).order_by('-latest')[offset:offset + limit]
|
|
else:
|
|
return Friendship.objects.filter(Q(source=user) | Q(target=user)).order_by('-latest')[offset:offset + limit]
|
|
else:
|
|
return Friendship.objects.filter(Q(source=user) | Q(target=user)).order_by('-created')[offset:offset + limit]
|
|
@staticmethod
|
|
def find_friendship(first, second):
|
|
return Friendship.objects.filter(Q(source=first) & Q(target=second) | Q(target=first) & Q(source=second)).order_by('-created').first()
|
|
@staticmethod
|
|
def get_friendships_message(user, limit=20, offset=0, online_only=False):
|
|
friends_list = Friendship.get_friendships(user, limit, offset, True, online_only)
|
|
friends = []
|
|
for friend in friends_list:
|
|
friends.append(friend.other(user))
|
|
del(friends_list)
|
|
for friend in friends:
|
|
friend.get_latest_msg = friend.get_latest_msg(user)
|
|
return friends
|
|
|
|
class Conversation(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
source = models.ForeignKey(User, related_name='conv_source', on_delete=models.CASCADE)
|
|
target = models.ForeignKey(User, related_name='conv_target', on_delete=models.CASCADE)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "conversation with " + str(self.source) + " and " + str(self.target)
|
|
def latest_message(self, user):
|
|
msgs = Message.objects.filter(conversation=self).order_by('-created')[:5]
|
|
if not msgs:
|
|
return False
|
|
message = msgs.first()
|
|
message.mine = message.mine(user)
|
|
return message
|
|
def unread(self, user):
|
|
return self.message_set.filter(read=False).exclude(creator=user).order_by('-created')
|
|
def set_read(self, user):
|
|
return self.unread(user).update(read=True)
|
|
def all_read(self):
|
|
return self.message_set.update(read=True)
|
|
def messages(self, request, limit=50, offset=0):
|
|
msgs = self.message_set.order_by('-created')[offset:offset + limit]
|
|
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='')
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
read = models.BooleanField(default=False)
|
|
is_rm = models.BooleanField(default=False)
|
|
creator = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
objects = PostManager()
|
|
real = models.Manager()
|
|
|
|
def __str__(self):
|
|
return self.body[:250]
|
|
def trun(self):
|
|
if self.is_rm:
|
|
return 'deleted'
|
|
if self.drawing:
|
|
return '(drawing)'
|
|
else:
|
|
return self.body
|
|
def mine(self, user):
|
|
if self.creator == user:
|
|
return True
|
|
return False
|
|
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
|
|
return json.dumps(ls)
|
|
|
|
class ConversationInvite(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE)
|
|
source = models.ForeignKey(User, related_name='convinvite_source', on_delete=models.CASCADE)
|
|
target = models.ForeignKey(User, related_name='convinvite_target', on_delete=models.CASCADE)
|
|
body = models.TextField(blank=True, null=True, default='')
|
|
read = models.BooleanField(default=False)
|
|
finished = models.BooleanField(default=False)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "Invite to conversation " + str(self.conversation) + " from " + str(self.source) + " to " + str(self.target)
|
|
|
|
class Poll(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
able_vote = models.BooleanField(default=True)
|
|
choices = models.TextField(default="[]")
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return "A poll created at " + str(self.created)
|
|
def num_votes(self):
|
|
return self.pollvote_set.count()
|
|
def vote(self, user, opt):
|
|
ex_query = self.pollvote_set.filter(by=user)
|
|
if ex_query.exists():
|
|
ex_query.first().delete()
|
|
self.pollvote_set.create(by=user, choice=opt)
|
|
def unvote(self, user):
|
|
vote = self.pollvote_set.filter(by=user).first()
|
|
if vote:
|
|
vote.delete()
|
|
def has_vote(self, user):
|
|
if not user.is_authenticated:
|
|
return False
|
|
vote = self.pollvote_set.filter(by=user)
|
|
if vote:
|
|
return (True, self.choices[vote.first().choice])
|
|
return False
|
|
def setup(self, user):
|
|
self.choices = json.loads(self.choices)
|
|
self.num_votes = self.num_votes()
|
|
self.has_vote = self.has_vote(user)
|
|
class PollVote(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
done = models.DateTimeField(auto_now_add=True)
|
|
choice = models.SmallIntegerField(default=0)
|
|
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
|
|
by = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
def __str__(self):
|
|
return "A vote on option " + str(self.choice) + " for poll \"" + str(self.poll) + "\" by " + str(self.by)
|
|
#def choice_votes(self):
|
|
# return PollVote.objects.filter(poll=self.poll, choice=self.choice).count()
|
|
|
|
|
|
# Login attempts:
|
|
class LoginAttempt(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
success = models.BooleanField(default=False)
|
|
addr = models.CharField(max_length=64, null=True, blank=True)
|
|
user_agent = models.TextField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return 'A login attempt to ' + str(self.user) + ' from ' + str(self.addr) + ', ' + str(self.success)
|
|
|
|
class MetaViews(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
target_user = models.ForeignKey(User, related_name='target_user', on_delete=models.CASCADE)
|
|
from_user = models.ForeignKey(User, related_name='from_user', on_delete=models.CASCADE)
|
|
def __str__(self):
|
|
return str(self.from_user) + ' viewed ' + str(self.target_user)
|
|
|
|
# Finally
|
|
class UserBlock(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
source = models.ForeignKey(User, related_name='block_source', on_delete=models.CASCADE)
|
|
target = models.ForeignKey(User, related_name='block_target', on_delete=models.CASCADE)
|
|
# ...???
|
|
#full = models.BooleanField(default=False)
|
|
|
|
def __str__(self):
|
|
return "Block created from " + str(self.source) + " to " + str(self.target)
|
|
|
|
@staticmethod
|
|
def find_block(first, second):
|
|
#, full=False):
|
|
# in every instance of find_block that I have seen, the second argument is always the request user
|
|
# so in the interest of making this implementation easy (forgot where the checks are supposed to go otherwise)
|
|
if not second.is_authenticated:
|
|
return False
|
|
#if full:
|
|
# return UserBlock.objects.filter(Q(source=first, full=full) & Q(target=second, full=full) | Q(target=first, full=full) & Q(source=second, full=full)).exists()
|
|
block = UserBlock.objects.filter(Q(source=first) & Q(target=second) | Q(target=first) & Q(source=second))
|
|
if not block.exists():
|
|
return False
|
|
return block.first()
|
|
|
|
class AuditLog(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
type = models.SmallIntegerField(choices=((0, "Post delete"), (1, "Comment delete"), (2, "User edit"), (3, "Disable comments"), (4, "Community edit"), (5, "Ban user"), (6, "Edit ban for user"), ))
|
|
post = models.ForeignKey(Post, related_name='audit_post', null=True, on_delete=models.CASCADE)
|
|
comment = models.ForeignKey(Comment, related_name='audit_comment', null=True, on_delete=models.CASCADE)
|
|
user = models.ForeignKey(User, related_name='audit_user', null=True, on_delete=models.CASCADE)
|
|
community = models.ForeignKey(Community, related_name='audit_community', null=True, on_delete=models.CASCADE)
|
|
reasoning = models.TextField(null=True, blank=True, default="")
|
|
by = models.ForeignKey(User, related_name='audit_by', on_delete=models.CASCADE)
|
|
reversed_by = models.ForeignKey(User, null=True, related_name='audit_reverse_by', on_delete=models.CASCADE)
|
|
|
|
def __str__(self):
|
|
return str(self.by) + " did " + self.get_type_display() + " at " + str(self.created)
|
|
def reverse(self, user=None):
|
|
# Try to reverse what this did
|
|
if user:
|
|
self.reversed_by = user
|
|
# No switches in Python, so
|
|
if self.type == 0:
|
|
self.post.is_rm = False
|
|
self.post.status = 0
|
|
self.post.save()
|
|
return True
|
|
elif self.type == 1:
|
|
self.post.is_rm = False
|
|
self.post.status = 0
|
|
self.post.save()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
class UserRequest(User):
|
|
# USER AGENT
|
|
ua = models.TextField(default='', null=True, blank=True)
|
|
latest = models.DateTimeField(auto_now=True)
|
|
status = models.SmallIntegerField(default=0, choices=((0, 'submitted'), (1, 'viewed'), (2, 'accepted'), (3, 'decline'), (4, 'ignore'), ))
|
|
|
|
class Ads(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
url = models.CharField(max_length=256, null=False, blank=False)
|
|
imageurl = models.ImageField(upload_to='ad/%y/%m/%d/', max_length=100)
|
|
|
|
def get_one():
|
|
ads = Ads.objects.all()
|
|
ad = random.choice(ads)
|
|
return ad
|
|
|
|
def ads_available():
|
|
global adsavailable
|
|
if(Ads.objects.all().exists()):
|
|
adsavailable = True
|
|
else:
|
|
adsavailable = False
|
|
return adsavailable
|
|
|
|
class Meta:
|
|
verbose_name_plural = "ads"
|
|
|
|
def __str__(self):
|
|
return "Ad with id " + str(self.id) + ", created at " + str(self.created) + ", with url " + str(self.url) + ", and imageurl " + str(self.imageurl)
|
|
|
|
# thing will log changes to your bio or nickname
|
|
class ProfileHistory(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
|
|
old_nickname = models.CharField(max_length=64, blank=True, null=True)
|
|
new_nickname = models.CharField(max_length=64, blank=True, null=True)
|
|
old_comment = models.TextField(blank=True, null=True)
|
|
new_comment = models.TextField(blank=True)
|
|
|
|
def __str__(self):
|
|
return str(self.user) + ' changed profile details'
|
|
|
|
class Meta:
|
|
verbose_name_plural = "profile histories"
|
|
|
|
# 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)
|
|
# 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)
|