diff --git a/ban/__init__.py b/ban/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ban/admin.py b/ban/admin.py
new file mode 100644
index 0000000..a355558
--- /dev/null
+++ b/ban/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+from .models import UsersBan
+
+# Register your models here.
+
+class UsersBanModel(admin.ModelAdmin):
+ list_display = ['user', 'ban', ]
+ list_filter = ('ban', )
+ search_fields = ('user__username', 'user__id', 'user__first_name', 'user__last_name')
+
+'''
+admin.site.register(UsersBan, UsersBanModel)
+'''
\ No newline at end of file
diff --git a/ban/apps.py b/ban/apps.py
new file mode 100644
index 0000000..9407187
--- /dev/null
+++ b/ban/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class BanConfig(AppConfig):
+ name = 'ban'
diff --git a/ban/middleware.py b/ban/middleware.py
new file mode 100644
index 0000000..4a69940
--- /dev/null
+++ b/ban/middleware.py
@@ -0,0 +1,22 @@
+from django.shortcuts import render
+from django.http import HttpResponseForbidden
+from .models import UsersBan
+
+class BanManagement():
+ """Users Management"""
+
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+
+ if(UsersBan.objects.all().filter(ban=True, user_id=request.user.id)):
+ return HttpResponseForbidden('Commit suicide')
+ elif request.user.is_authenticated:
+ if not request.user.is_active():
+ return HttpResponseForbidden(request.user.warned_reason)
+ else:
+ return response
+ else:
+ return response
diff --git a/ban/migrations/0001_initial.py b/ban/migrations/0001_initial.py
new file mode 100644
index 0000000..3c52bc7
--- /dev/null
+++ b/ban/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.3 on 2022-02-28 20:18
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UsersBan',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('ban', models.BooleanField(default=False, help_text='Users Bans', verbose_name='Ban')),
+ ('user', models.ForeignKey(help_text='Choose Username', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User name')),
+ ],
+ options={
+ 'verbose_name_plural': 'Ban Management',
+ 'ordering': ('user',),
+ },
+ ),
+ ]
diff --git a/ban/migrations/0002_usersban_reason.py b/ban/migrations/0002_usersban_reason.py
new file mode 100644
index 0000000..da4314d
--- /dev/null
+++ b/ban/migrations/0002_usersban_reason.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2022-09-03 21:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ban', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='usersban',
+ name='reason',
+ field=models.CharField(blank=True, max_length=1024, null=True),
+ ),
+ ]
diff --git a/ban/migrations/0003_remove_usersban_reason.py b/ban/migrations/0003_remove_usersban_reason.py
new file mode 100644
index 0000000..e629295
--- /dev/null
+++ b/ban/migrations/0003_remove_usersban_reason.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.3 on 2022-09-03 21:03
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ban', '0002_usersban_reason'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='usersban',
+ name='reason',
+ ),
+ ]
diff --git a/ban/migrations/__init__.py b/ban/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ban/migrations/__pycache__/0001_initial.cpython-310.pyc b/ban/migrations/__pycache__/0001_initial.cpython-310.pyc
new file mode 100644
index 0000000..7f0eb0c
Binary files /dev/null and b/ban/migrations/__pycache__/0001_initial.cpython-310.pyc differ
diff --git a/ban/migrations/__pycache__/0002_usersban_reason.cpython-310.pyc b/ban/migrations/__pycache__/0002_usersban_reason.cpython-310.pyc
new file mode 100644
index 0000000..f5fbf7a
Binary files /dev/null and b/ban/migrations/__pycache__/0002_usersban_reason.cpython-310.pyc differ
diff --git a/ban/migrations/__pycache__/0003_remove_usersban_reason.cpython-310.pyc b/ban/migrations/__pycache__/0003_remove_usersban_reason.cpython-310.pyc
new file mode 100644
index 0000000..f5dd738
Binary files /dev/null and b/ban/migrations/__pycache__/0003_remove_usersban_reason.cpython-310.pyc differ
diff --git a/ban/migrations/__pycache__/__init__.cpython-310.pyc b/ban/migrations/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..2a6d6cb
Binary files /dev/null and b/ban/migrations/__pycache__/__init__.cpython-310.pyc differ
diff --git a/ban/models.py b/ban/models.py
new file mode 100644
index 0000000..afe9c1e
--- /dev/null
+++ b/ban/models.py
@@ -0,0 +1,16 @@
+from django.db import models
+from django.conf import settings
+
+# Create your models here.
+
+class UsersBan(models.Model):
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="User name", help_text="Choose Username", on_delete=models.CASCADE)
+
+ ban = models.BooleanField(default=False, verbose_name="Ban", help_text="Users Bans")
+
+ class Meta:
+ verbose_name_plural = "Ban Management"
+ ordering = ('user', )
+
+ def __str__(self):
+ return '{}'.format(self.user)
diff --git a/ban/templates/ban.html b/ban/templates/ban.html
new file mode 100644
index 0000000..e104cf3
--- /dev/null
+++ b/ban/templates/ban.html
@@ -0,0 +1,7 @@
+
+
+
+
+commit suicide
+
+
\ No newline at end of file
diff --git a/ban/tests.py b/ban/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/ban/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/ban/views.py b/ban/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/ban/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/closedverse/__init__.py b/closedverse/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/closedverse/settings.py b/closedverse/settings.py
new file mode 100644
index 0000000..b687ae9
--- /dev/null
+++ b/closedverse/settings.py
@@ -0,0 +1,240 @@
+"""
+Django settings for closedverse project.
+
+Generated by 'django-admin startproject' using Django 1.10.7.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.10/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.10/ref/settings/
+"""
+# Django Settings
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = False
+PROD = False
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret! (If you want a website that can generate key https://miniwebtool.com/django-secret-key-generator/ goto here.)
+SECRET_KEY = ''
+
+# Change the ALLOWED_HOSTS to your liking.
+
+ALLOWED_HOSTS = [
+ 'domain.com'
+]
+
+DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'admin_interface',
+ 'colorfield',
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'markdown_deux',
+ 'closedverse_main',
+ 'ban',
+ 'maintenance',
+]
+X_FRAME_OPTIONS='SAMEORIGIN'
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'ban.middleware.BanManagement',
+ 'closedverse_main.middleware.ClosedMiddleware',
+ 'maintenance.middleware.MaintenanceManagement',
+ 'whitenoise.middleware.WhiteNoiseMiddleware'
+]
+
+ROOT_URLCONF = 'closedverse.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'closedverse.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR + '/sql.sqlite3',
+ }
+}
+# log errors.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'file': {
+ 'level': 'ERROR',
+ 'class': 'logging.FileHandler',
+ 'filename': 'logs/errors.log',
+ },
+ },
+ 'loggers': {
+ 'django': {
+ 'handlers': ['file'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ },
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.10/topics/i18n/
+
+TIME_ZONE = 'PST8PDT'
+
+LANGUAGE_CODE = 'en-us'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+# You can use Mailtrap to get the email system working. Mailtrap is free and will work well for what you'll be doing with this.
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+EMAIL_HOST = 'send.smtp.mailtrap.io'
+EMAIL_HOST_USER = 'api'
+EMAIL_HOST_PASSWORD = ''
+EMAIL_PORT = ''
+EMAIL_USE_TLS = True
+DEFAULT_FROM_EMAIL = 'noreply@domain'
+
+AUTH_USER_MODEL = 'closedverse_main.User'
+CSRF_FAILURE_VIEW = 'closedverse_main.views.csrf_fail'
+LOGIN_URL = '/login/'
+LOGIN_REDIRECT_URL = '/login/'
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.10/howto/static-files/
+STATICFILES_DIRS = [
+ os.path.join(BASE_DIR, 'static/')
+]
+STATIC_URL = '/s/'
+STATIC_ROOT = os.path.join(BASE_DIR, 's/')
+STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'
+
+# Media root
+# This MUST end with a trailing slash and there must be an 'rm' folder in it
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = '/i/'
+
+# Cedar Three settings
+# reCAPTCHA v2 keys, None for no reCAPTCHA
+recaptcha_pub = ''
+recaptcha_priv = ''
+
+# NNID forbidden list. None for no passing.
+nnid_forbiddens = BASE_DIR + '/forbidden.json'
+
+# Memo title and message on communities list
+# I got rid of this setting because it was annoying i guess, edit the HTML to change it. -seedur
+
+# Client key to use for iphub.email, because we're using that
+# None for no IP checking (recommended since this is so slow)
+iphub_key = ""
+
+# If IP can be checked, then use this to disallow any proxies
+disallow_proxy = False
+
+# MD
+MARKDOWN_DEUX_STYLES = {
+ "default": {
+ "extras": {
+ "code-friendly": None,
+ },
+ "safe_mode": "escape",
+ },
+}
+
+# allow sign ups.
+allow_signups = True
+
+# Minimum level required to view IP addresses and user agents. (default: 10)
+# Mods under this level will still be able to manage users, however will not be able to view any sensitive data.
+min_lvl_metadata_perms = 10
+
+# file size limits in megabytes! only applies when using the community tools.
+max_icon_size = .5
+max_banner_size = 1
+
+# The minimum length required for a user's password. This is to save the users from themselves in the event of a data breach. The longer and more complex the password is, the harder it is to be cracked. (default: 7)
+# This will definitely miss a few people off who just want to sign up without worrying about long passwords.
+minimum_password_length = 7
+
+# 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 = 15728640
+
+# 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 = "#ff00cd"
+
+# The location to redirect to if a user's status is set to 2 (Redirect) (rickroll)
+inactive_redirect = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+
+# Option to delete image if it's local
+# 0 - keep, 1 - move to 'rm' folder, 2 - DELETE
+image_delete_opt = 0
+
+# age (minimal 13 due to C.O.P.P.A)
+age_allowed = "16"
diff --git a/closedverse/urls.py b/closedverse/urls.py
new file mode 100644
index 0000000..ffe183a
--- /dev/null
+++ b/closedverse/urls.py
@@ -0,0 +1,30 @@
+"""closedverse URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.conf.urls import url, include
+ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url, include
+
+from django.contrib import admin
+from .settings import INSTALLED_APPS, MEDIA_ROOT, MEDIA_URL
+from django.conf.urls.static import static
+handler500 = 'closedverse_main.views.server_err'
+urlpatterns = [
+ url(r'^admin/', admin.site.urls),
+ url(r'^', include('closedverse_main.urls'))
+]
+
+if 'silk' in INSTALLED_APPS:
+ urlpatterns += [url(r'^silk/', include('silk.urls', namespace='silk'))]
+
+urlpatterns += static(MEDIA_URL, document_root=MEDIA_ROOT)
diff --git a/closedverse/wsgi.py b/closedverse/wsgi.py
new file mode 100644
index 0000000..1208e5b
--- /dev/null
+++ b/closedverse/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for closedverse project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "closedverse.settings")
+
+application = get_wsgi_application()
diff --git a/closedverse_main/__init__.py b/closedverse_main/__init__.py
new file mode 100644
index 0000000..c232b53
--- /dev/null
+++ b/closedverse_main/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'closedverse_main.apps.ClosedverseMainConfig'
diff --git a/closedverse_main/admin.py b/closedverse_main/admin.py
new file mode 100644
index 0000000..be79a2c
--- /dev/null
+++ b/closedverse_main/admin.py
@@ -0,0 +1,161 @@
+from django.contrib import admin
+#from django.contrib.auth.admin import UserAdmin
+from django.contrib.auth.models import Group
+from django.forms import ModelForm, PasswordInput
+from closedverse_main import models
+from closedverse import settings
+
+from . import models
+
+# Override admin login page
+from closedverse_main.views import login_page
+admin.autodiscover()
+admin.site.login = login_page
+
+"""
+class UserForm(ModelForm):
+ class Meta:
+ model = models.User
+ fields = '__all__'
+ widgets = {
+ 'password': PasswordInput(),
+ }
+"""
+@admin.action(description='Hide selected items')
+def Hide_Memo(modeladmin, request, queryset):
+ queryset.update(show = False)
+@admin.action(description='Show selected items')
+def Show_Memo(modeladmin, request, queryset):
+ queryset.update(show = True)
+@admin.action(description='Hide selected items')
+def Hide_content(modeladmin, request, queryset):
+ queryset.update(is_rm = True)
+@admin.action(description='Show selected items')
+def Show_content(modeladmin, request, queryset):
+ queryset.update(is_rm = False)
+@admin.action(description='Feature selected communities')
+def Feature_community(modeladmin, request, queryset):
+ queryset.update(is_feature = True)
+@admin.action(description='Unfeature selected communities')
+def Unfeature_community(modeladmin, request, queryset):
+ queryset.update(is_feature = False)
+@admin.action(description='Force login')
+def force_login(modeladmin, request, queryset):
+ queryset.update(require_auth = True)
+@admin.action(description='Unforce login')
+def unforce_login(modeladmin, request, queryset):
+ queryset.update(require_auth = False)
+@admin.action(description='Disable user')
+def Disable_user(modeladmin, request, queryset):
+ queryset.update(active = False)
+
+class UserAdmin(admin.ModelAdmin):
+ search_fields = ('id', 'unique_id', 'username', 'nickname', 'email', )
+ list_display = ('id', 'username', 'nickname', 'warned', 'level', 'staff', 'active', )
+ exclude = ('addr', 'signup_addr', 'password', )
+ actions = [Disable_user]
+ #exclude = ('staff', )
+ # Not yet
+ #form = UserForm
+class ProfileAdmin(admin.ModelAdmin):
+ search_fields = ('id', 'unique_id', 'origin_id', )
+ raw_id_fields = ('user', 'favorite', 'adopted', )
+ list_display = ('id', 'user', 'comment', 'let_freedom', )
+
+class ComplaintAdmin(admin.ModelAdmin):
+ search_fields = ('id', 'unique_id', 'body', )
+ raw_id_fields = ('creator', )
+class ConversationAdmin(admin.ModelAdmin):
+ search_fields = ('id', 'unique_id', )
+ raw_id_fields = ('source', 'target', )
+
+class PostAdmin(admin.ModelAdmin):
+ raw_id_fields = ('creator', 'poll', )
+ search_fields = ('id', 'unique_id', 'body', 'creator__username', )
+ list_display = ('id', 'creator', 'body', 'is_rm', )
+ actions = [Hide_content, Show_content]
+ def get_queryset(self, request):
+ return models.Post.real.get_queryset()
+
+class CommentAdmin(admin.ModelAdmin):
+ raw_id_fields = ('creator', 'original_post', )
+ search_fields = ('id', 'unique_id', 'body', 'creator__username', )
+ list_display = ('id', 'creator', 'body', 'original_post', 'is_rm', )
+ actions = [Hide_content, Show_content]
+ def get_queryset(self, request):
+ return models.Comment.real.get_queryset()
+
+class CommunityAdmin(admin.ModelAdmin):
+ raw_id_fields = ('creator', )
+ list_display = ('id', 'name', 'description', 'type', 'creator', 'is_rm', 'is_feature', 'require_auth')
+ search_fields = ('id', 'unique_id', 'name', 'description', )
+ actions = [Hide_content, Show_content, Feature_community, Unfeature_community, force_login, unforce_login]
+ def get_queryset(self, request):
+ return models.Community.real.get_queryset()
+
+class MessageAdmin(admin.ModelAdmin):
+ raw_id_fields = ('creator', 'conversation', )
+ search_fields = ('id', 'unique_id', 'body', 'creator__username', )
+ list_display = ('id', 'creator', 'conversation', 'body', )
+ actions = [Hide_content, Show_content]
+ def get_queryset(self, request):
+ return models.Message.real.get_queryset()
+
+class NotificationAdmin(admin.ModelAdmin):
+ raw_id_fields = ('to', 'source', 'context_post', 'context_comment', )
+ search_fields = ('unique_id', )
+ list_display = ('id', 'to', 'source', 'context_post', 'context_comment', )
+
+class AuditAdmin(admin.ModelAdmin):
+ raw_id_fields = ('by', 'user', 'post', 'comment', 'reversed_by', )
+ search_fields = ('by__username', 'user__username', )
+
+class AdsAdmin(admin.ModelAdmin):
+ raw_id_fileds = ('id', 'created', 'url', 'imageurl')
+
+class MOTDAdmin(admin.ModelAdmin):
+ raw_id_fileds = ('id', 'created', 'message', )
+ list_display = ('message', 'show', 'order', 'created', )
+ actions = [Hide_Memo, Show_Memo]
+
+class WelcomemsgAdmin(admin.ModelAdmin):
+ raw_id_fileds = ('id', 'created', 'message', )
+ list_display = ('Title', 'message', 'show', 'order', 'created', )
+ actions = [Hide_Memo, Show_Memo]
+
+class YeahAdmin(admin.ModelAdmin):
+ raw_id_fields = ('by', 'post', 'comment', )
+ list_display = ('by', 'post', 'comment', )
+ search_fields = ('by__username', 'post__body', 'comment__body', )
+
+class HistoryAdmin(admin.ModelAdmin):
+ raw_id_fields = ('user',)
+ list_display = ('id', 'user')
+
+#class BlockAdmin(admin.ModelAdmin)
+
+admin.site.unregister(Group)
+
+admin.site.register(models.User, UserAdmin)
+admin.site.register(models.Profile, ProfileAdmin)
+admin.site.register(models.Community, CommunityAdmin)
+admin.site.register(models.Complaint, ComplaintAdmin)
+admin.site.register(models.Message, MessageAdmin)
+admin.site.register(models.Conversation, ConversationAdmin)
+admin.site.register(models.Notification, NotificationAdmin)
+admin.site.register(models.UserBlock)
+admin.site.register(models.AuditLog, AuditAdmin)
+admin.site.register(models.ProfileHistory, HistoryAdmin)
+
+
+admin.site.register(models.Post, PostAdmin)
+admin.site.register(models.Comment, CommentAdmin)
+admin.site.register(models.Ads, AdsAdmin)
+admin.site.register(models.Motd, MOTDAdmin)
+admin.site.register(models.welcomemsg, WelcomemsgAdmin)
+admin.site.register(models.Yeah, YeahAdmin)
+admin.site.register(models.Follow)
+admin.site.register(models.FriendRequest)
+admin.site.register(models.Friendship, ConversationAdmin)
+admin.site.register(models.Poll)
+admin.site.register(models.PollVote)
diff --git a/closedverse_main/apps.py b/closedverse_main/apps.py
new file mode 100644
index 0000000..5024b69
--- /dev/null
+++ b/closedverse_main/apps.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class ClosedverseMainConfig(AppConfig):
+ name = 'closedverse_main'
+ verbose_name = "Cedar"
diff --git a/closedverse_main/context_processors.py b/closedverse_main/context_processors.py
new file mode 100644
index 0000000..e69de29
diff --git a/closedverse_main/middleware.py b/closedverse_main/middleware.py
new file mode 100644
index 0000000..880d444
--- /dev/null
+++ b/closedverse_main/middleware.py
@@ -0,0 +1,72 @@
+from django.http import HttpResponseForbidden, HttpResponseBadRequest
+from closedverse import settings
+from django.shortcuts import render
+from django.shortcuts import redirect
+from django.contrib.auth import logout
+from re import compile
+
+class ClosedMiddleware(object):
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+ if request.user.is_authenticated:
+ if not request.user.profile():
+ return HttpResponseForbidden('So, Somehow your profile is completely gone. Your account itself still exists, but your profile does not. Please contact an admin, and ask them to make a profile for you. If you are unable to contact someone, you should make a new account.')
+ else:
+ return response
+ '''
+ for keyword in ['24.61.157.95', ]:
+ if keyword in request.META['HTTP_CF_CONNECTING_IP']:
+ return redirect('https://file.garden/Xbo5elapeDSxWf1x/adam.mp4')
+
+ for keyword in ['PlayStation', 'Switch', '3DS', ]:
+ if keyword in request.META['HTTP_USER_AGENT']:
+ return HttpResponseForbidden('Error code 403')
+ '''
+ else:
+ return response
+
+# Taken from https://python-programming.com/recipes/django-require-authentication-pages/
+'''
+class ClosedMiddleware(object):
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ # Force logins if it's set
+ if settings.force_login and not request.user.is_authenticated:
+ if not any(m.match(request.path_info.lstrip('/')) for m in EXEMPT_URLS):
+ if request.is_ajax():
+ return HttpResponseForbidden("Login is required")
+ return redirect(settings.LOGIN_REDIRECT_URL)
+ # Fix this ; put something in settings signifying if the server supports HTTPS or not
+ if not request.is_secure() and (not settings.DEBUG) and settings.PROD:
+ # Let's try to redirect to HTTPS for non-Nintendo stuff.
+ if not request.META.get('HTTP_USER_AGENT'):
+ return HttpResponseForbidden("You need a user agent.", content_type='text/plain')
+ if not request.is_secure() and not 'Nintendo' in request.META['HTTP_USER_AGENT']:
+ return redirect('https://{0}{1}'.format(request.get_host(), request.get_full_path()))
+ if request.user.is_authenticated:
+ # User active; this doesn't work at the moment due to Postgres not being able to change bools to ints
+ if request.user.is_active() == 0:
+ return HttpResponseForbidden()
+ elif request.user.is_active() == 2:
+ return redirect(settings.inactive_redirect)
+
+ if not request.user.is_active:
+ return HttpResponseForbidden()
+ # If there isn't a request.session
+ if not request.session.get('passwd'):
+ request.session['passwd'] = request.user.password
+ else:
+ if request.session['passwd'] != request.user.password:
+ logout(request)
+ response = self.get_response(request)
+
+ return response
+ '''
+
+"""
+return HttpResponseForbidden("You're one sick fuck. I would never suggest removing an Inkling girl's clothes and licking her tiny body all over, nibbling her neck and kissing her adorable little nipples. Only a heartless monster would think about her cute girlish mouth and tongue wrapped around a thick cock slick with her saliva, pumping in and out of her mouth until it erupts, the cum more than her little throat can swallow. The idea of thick viscous semen overflowing, dribbling down her chin over her flat chest, her tiny hands scooping it all up and watching her suck it off her fingertips is just horrible. You're all a bunch of sick perverts, thinking of spreading her smooth slender thighs, cock poised at the entrance to her pure, tight, virginal pussy, and thrusting in deep as a whimper escapes her lips which are slippery with cum, while her small body shudders from having her cherry taken in one quick stroke. I am disgusted at how you'd get even more excited as you lean over her, listening to her quickening breath, her girlish moans and gasps while you hasten your strokes, her sweet pants warm and moist on your face and her flat chest, shiny with a sheen of fresh sweat, rising and falling rapidly to meet yours. It is truly nasty how you'd run your hands all over her tiny body while you violate her, feeling her nipples hardening against your tongue as you lick her chest, her neck and her armpits, savoring the scent of her skin and sweat while she trembles from the stimulation and as she reaches her climax, hearing her cry out softly as she has her first orgasm while that cock is buried impossibly deep inside her, pulsing violently as an intense amount of hot cum spurts forth and floods through her freshly-deflowered pussy for the first time, filling her womb only to spill out of her with a sickening squelch. And as you lie atop her flushed body, she murmurs breathlessly, \"You came so much inside of me,\" then her fingers dig into your back as she feels your cock hardening inside again.", content_type='text/plain')"""
diff --git a/closedverse_main/migrations/0001_initial.py b/closedverse_main/migrations/0001_initial.py
new file mode 100644
index 0000000..f5a3673
--- /dev/null
+++ b/closedverse_main/migrations/0001_initial.py
@@ -0,0 +1,433 @@
+# Generated by Django 3.2.3 on 2023-02-07 01:50
+
+import closedverse_main.models
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('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(blank=True, default='', max_length=254, null=True)),
+ ('has_mh', models.BooleanField(default=False)),
+ ('avatar', models.CharField(blank=True, default='', max_length=1200)),
+ ('theme', closedverse_main.models.ColorField(blank=True, max_length=18, null=True)),
+ ('level', models.SmallIntegerField(default=0)),
+ ('role', models.SmallIntegerField(choices=[(0, 'normal'), (1, 'Bot'), (2, 'Administrator'), (3, 'Moderator'), (4, 'NO'), (5, 'Donator'), (6, 'Tester'), (7, 'Cools'), (8, 'Developer'), (9, 'SMF9-Django'), (10, 'Staff'), (11, 'GAY DOGWATER'), (12, 'DUMB SNAIL'), (13, 'Russian ADRIAN'), (14, 'Contest'), (15, 'Gamecon'), (16, 'Cedar')], default=0)),
+ ('addr', models.CharField(blank=True, max_length=64, null=True)),
+ ('signup_addr', models.CharField(blank=True, max_length=64, null=True)),
+ ('user_agent', models.TextField(blank=True, null=True)),
+ ('c_tokens', models.IntegerField(default=1)),
+ ('hide_online', models.BooleanField(default=False)),
+ ('color', closedverse_main.models.ColorField(blank=True, default='', max_length=18, null=True)),
+ ('staff', models.BooleanField(default=False)),
+ ('active', models.BooleanField(default=True)),
+ ('warned', models.BooleanField(default=False)),
+ ('warned_reason', models.CharField(blank=True, max_length=600, null=True)),
+ ('bg_url', models.CharField(blank=True, max_length=300, null=True)),
+ ('last_login', models.DateTimeField(auto_now=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Ads',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('url', models.CharField(max_length=256)),
+ ('imageurl', models.ImageField(upload_to='ad/%y/%m/%d/')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Comment',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('feeling', models.SmallIntegerField(choices=[(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!')], default=0)),
+ ('body', models.TextField(null=True)),
+ ('screenshot', models.CharField(blank=True, default='', max_length=1200, null=True)),
+ ('drawing', models.CharField(blank=True, max_length=200, null=True)),
+ ('spoils', models.BooleanField(default=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('edited', models.DateTimeField(auto_now=True)),
+ ('befores', models.TextField(blank=True, null=True)),
+ ('has_edit', models.BooleanField(default=False)),
+ ('is_rm', models.BooleanField(default=False)),
+ ('status', models.SmallIntegerField(choices=[(0, 'ok'), (1, 'delete by user'), (2, 'delete by authority'), (3, 'delete by mod'), (4, 'delete by admin'), (5, 'account pruge')], default=0)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Community',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=255)),
+ ('description', models.TextField(blank=True, default='')),
+ ('ico', models.ImageField(blank=True, null=True, upload_to='icon/%y/%m/%d/')),
+ ('banner', models.ImageField(blank=True, null=True, upload_to='banner/%y/%m/%d/')),
+ ('type', models.SmallIntegerField(choices=[(0, 'General'), (1, 'Game'), (2, 'Special'), (3, 'User Community'), (4, 'Hide')], default=0)),
+ ('platform', models.SmallIntegerField(choices=[(0, 'none'), (1, '3ds'), (2, 'wii u'), (3, 'switch'), (4, 'both'), (5, 'PC'), (6, 'Xbox'), (7, 'Playstation')], default=0)),
+ ('tags', models.CharField(blank=True, choices=[('announcements', 'main announcement community'), ('changelog', 'main changelog'), ('activity', 'Activity Feed posting community'), ('general', 'General Discussion Community')], max_length=255, null=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('updated', models.DateTimeField(auto_now=True)),
+ ('is_rm', models.BooleanField(default=False)),
+ ('is_feature', models.BooleanField(default=False)),
+ ('require_auth', models.BooleanField(default=False)),
+ ('allowed_users', models.TextField(blank=True, null=True)),
+ ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name_plural': 'communities',
+ },
+ ),
+ migrations.CreateModel(
+ name='Conversation',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conv_source', to=settings.AUTH_USER_MODEL)),
+ ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conv_target', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Motd',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('order', models.IntegerField(default=1, max_length=3)),
+ ('show', models.BooleanField(default=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('Title', models.CharField(blank=True, default='Title', max_length=256)),
+ ('message', models.TextField()),
+ ('hide_date', models.BooleanField(default=False)),
+ ('image', models.ImageField(blank=True, max_length=255, null=True, upload_to='MOTD/%y/%m/%d/')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Poll',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('able_vote', models.BooleanField(default=True)),
+ ('choices', models.TextField(default='[]')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Post',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('feeling', models.SmallIntegerField(choices=[(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!')], default=0)),
+ ('body', models.TextField(null=True)),
+ ('drawing', models.CharField(blank=True, max_length=200, null=True)),
+ ('screenshot', models.CharField(blank=True, default='', max_length=1200, null=True)),
+ ('url', models.URLField(blank=True, default='', max_length=1200, null=True)),
+ ('spoils', models.BooleanField(default=False)),
+ ('disable_yeah', models.BooleanField(default=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('edited', models.DateTimeField(auto_now=True)),
+ ('befores', models.TextField(blank=True, null=True)),
+ ('has_edit', models.BooleanField(default=False)),
+ ('is_rm', models.BooleanField(default=False)),
+ ('status', models.SmallIntegerField(choices=[(0, 'ok'), (1, 'delete by user'), (2, 'delete by authority'), (3, 'delete by mod'), (4, 'delete by admin'), (5, 'account pruge')], default=0)),
+ ('community', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.community')),
+ ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('poll', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.poll')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='welcomemsg',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('order', models.IntegerField(default=1, max_length=3)),
+ ('show', models.BooleanField(default=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('Title', models.CharField(default='Title', max_length=256)),
+ ('message', models.TextField()),
+ ('image', models.ImageField(blank=True, max_length=255, null=True, upload_to='welcomemsg/%y/%m/%d/')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='UserRequest',
+ fields=[
+ ('user_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='closedverse_main.user')),
+ ('ua', models.TextField(blank=True, default='', null=True)),
+ ('latest', models.DateTimeField(auto_now=True)),
+ ('status', models.SmallIntegerField(choices=[(0, 'submitted'), (1, 'viewed'), (2, 'accepted'), (3, 'decline'), (4, 'ignore')], default=0)),
+ ],
+ bases=('closedverse_main.user',),
+ ),
+ migrations.CreateModel(
+ name='Yeah',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
+ ('type', models.SmallIntegerField(choices=[(0, 'post'), (1, 'comment')], default=0)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.comment')),
+ ('post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.post')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='UserBlock',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('full', models.BooleanField(default=False)),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='block_source', to=settings.AUTH_USER_MODEL)),
+ ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='block_target', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ThermostatTouch',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('lvl', models.IntegerField(default=1)),
+ ('who', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Restriction',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('type', models.SmallIntegerField(choices=[(0, 'Prevent yeah'), (1, 'Prevent follow'), (2, 'Prevent comment')])),
+ ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='restriction_by', to=settings.AUTH_USER_MODEL)),
+ ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='restriction_comment', to='closedverse_main.comment')),
+ ('post', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='restriction_post', to='closedverse_main.post')),
+ ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='restriction_user', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='RedFlag',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('type', models.SmallIntegerField(choices=[(0, 'Post'), (1, 'Comment'), (2, 'User')])),
+ ('reason', models.SmallIntegerField(choices=[(0, 'Actual harassment'), (1, 'Spam'), (2, "I don't like this"), (3, 'Personal info'), (4, 'Obscene use of swearing'), (5, 'NSFW where not allowed'), (6, 'Overly advertising/spam'), (7, 'Please delete this')])),
+ ('reasoning', models.TextField(blank=True, default='', null=True)),
+ ('comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.comment')),
+ ('post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.post')),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ProfileHistory',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('old_nickname', models.CharField(blank=True, max_length=64)),
+ ('new_nickname', models.CharField(blank=True, max_length=64)),
+ ('old_comment', models.TextField(blank=True)),
+ ('new_comment', models.TextField(blank=True)),
+ ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Profile',
+ fields=[
+ ('is_new', models.BooleanField(default=True)),
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('origin_id', models.CharField(blank=True, max_length=16, null=True)),
+ ('origin_info', models.CharField(blank=True, max_length=255, null=True)),
+ ('comment', models.TextField(blank=True, default='')),
+ ('country', models.CharField(blank=True, default='', max_length=120)),
+ ('whatareyou', models.CharField(blank=True, default='', max_length=120)),
+ ('id_visibility', models.SmallIntegerField(choices=[(0, 'show'), (1, 'friends only'), (2, 'hide')], default=0)),
+ ('pronoun_is', models.IntegerField(choices=[(0, "I don't know"), (1, 'He/him'), (2, 'She/her'), (3, 'He/she'), (4, 'It'), (5, 'They/Them')], default=0)),
+ ('gender_is', models.IntegerField(choices=[(0, "I don't know"), (1, 'Girl'), (2, 'Boy'), (3, 'Minion'), (4, 'Toaster'), (5, 'Dinosaur'), (6, 'Truck'), (7, 'Robot'), (8, 'Monkey'), (9, 'Big chungus'), (10, 'Other')], default=0)),
+ ('let_friendrequest', models.SmallIntegerField(choices=[(0, 'show'), (1, 'friends only'), (2, 'hide')], default=0)),
+ ('yeahs_visibility', models.SmallIntegerField(choices=[(0, 'show'), (1, 'friends only'), (2, 'hide')], default=0)),
+ ('comments_visibility', models.SmallIntegerField(choices=[(0, 'show'), (1, 'friends only'), (2, 'hide')], default=2)),
+ ('weblink', models.CharField(blank=True, default='', max_length=1200)),
+ ('external', models.CharField(blank=True, default='', max_length=255)),
+ ('let_yeahnotifs', models.BooleanField(default=True)),
+ ('let_freedom', models.BooleanField(default=True)),
+ ('limit_post', models.SmallIntegerField(default=0)),
+ ('cannot_edit', models.BooleanField(default=False)),
+ ('email_login', models.SmallIntegerField(choices=[(0, 'Do not allow'), (1, 'Okay'), (2, 'Only allow')], default=1)),
+ ('adopted', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to=settings.AUTH_USER_MODEL)),
+ ('favorite', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='closedverse_main.post')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PollVote',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('done', models.DateTimeField(auto_now_add=True)),
+ ('choice', models.SmallIntegerField(default=0)),
+ ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('poll', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.poll')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Notification',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('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')])),
+ ('merges', models.TextField(blank=True, default='')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('latest', models.DateTimeField(auto_now=True)),
+ ('context_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.comment')),
+ ('context_post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.post')),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_sender', to=settings.AUTH_USER_MODEL)),
+ ('to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_to', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Message',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('feeling', models.SmallIntegerField(choices=[(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!')], default=0)),
+ ('body', models.TextField(null=True)),
+ ('drawing', models.CharField(blank=True, max_length=200, null=True)),
+ ('screenshot', models.CharField(blank=True, default='', max_length=1200, null=True)),
+ ('url', models.URLField(blank=True, default='', max_length=1200, null=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('read', models.BooleanField(default=False)),
+ ('is_rm', models.BooleanField(default=False)),
+ ('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.conversation')),
+ ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='LoginAttempt',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('success', models.BooleanField(default=False)),
+ ('addr', models.CharField(blank=True, max_length=64, null=True)),
+ ('user_agent', models.TextField(blank=True, null=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Friendship',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('latest', models.DateTimeField(auto_now=True)),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friend_source', to=settings.AUTH_USER_MODEL)),
+ ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friend_target', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='FriendRequest',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('body', models.TextField(blank=True, default='', null=True)),
+ ('read', models.BooleanField(default=False)),
+ ('finished', models.BooleanField(default=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fr_source', to=settings.AUTH_USER_MODEL)),
+ ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fr_target', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Follow',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='follow_source', to=settings.AUTH_USER_MODEL)),
+ ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='follow_target', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ConversationInvite',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('body', models.TextField(blank=True, default='', null=True)),
+ ('read', models.BooleanField(default=False)),
+ ('finished', models.BooleanField(default=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.conversation')),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='convinvite_source', to=settings.AUTH_USER_MODEL)),
+ ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='convinvite_target', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Complaint',
+ fields=[
+ ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('type', models.SmallIntegerField(choices=[(0, 'Bug report'), (1, 'Suggestion'), (2, 'Want')])),
+ ('body', models.TextField(blank=True, default='')),
+ ('sex', models.SmallIntegerField(choices=[(0, 'girl'), (1, 'privileged one'), (2, '(none)')], null=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CommunityFavorite',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('community', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.community')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CommunityClink',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('kind', models.BooleanField(default=False)),
+ ('also', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='two', to='closedverse_main.community')),
+ ('root', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='one', to='closedverse_main.community')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='community',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.community'),
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='creator',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='original_post',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='closedverse_main.post'),
+ ),
+ migrations.CreateModel(
+ name='AuditLog',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('type', models.SmallIntegerField(choices=[(0, 'Post delete'), (1, 'Comment delete'), (2, 'User edit'), (3, 'Generate passwd reset'), (4, 'User delete'), (5, 'Image delete'), (6, 'Purge 1'), (7, 'Purge 2'), (8, 'Purge 3'), (9, 'Purge 4'), (10, 'Purge 5'), (11, 'Un-purge 1'), (12, 'Changed server settings')])),
+ ('reasoning', models.TextField(blank=True, default='', null=True)),
+ ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='audit_by', to=settings.AUTH_USER_MODEL)),
+ ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='audit_comment', to='closedverse_main.comment')),
+ ('post', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='audit_post', to='closedverse_main.post')),
+ ('reversed_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='audit_reverse_by', to=settings.AUTH_USER_MODEL)),
+ ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='audit_user', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/closedverse_main/migrations/0002_metaviews.py b/closedverse_main/migrations/0002_metaviews.py
new file mode 100644
index 0000000..389d414
--- /dev/null
+++ b/closedverse_main/migrations/0002_metaviews.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.2.3 on 2023-02-08 00:56
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('closedverse_main', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='MetaViews',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_user', to=settings.AUTH_USER_MODEL)),
+ ('target_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='target_user', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/closedverse_main/migrations/__init__.py b/closedverse_main/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/closedverse_main/models.py b/closedverse_main/models.py
new file mode 100644
index 0000000..ddfe649
--- /dev/null
+++ b/closedverse_main/models.py
@@ -0,0 +1,1799 @@
+from __future__ import unicode_literals
+from colorfield.fields import ColorField
+from django.db import models
+from django.contrib.auth.models import BaseUserManager
+from django.db.models import Q, QuerySet, Max, F, Count, Case, When, Exists, OuterRef
+from django.utils import timezone
+from django.forms.models import model_to_dict
+from django.utils.dateformat import format
+from django.core.validators import RegexValidator, URLValidator
+from django.core.exceptions import ValidationError
+from datetime import timedelta, datetime, date, time
+from passlib.hash import bcrypt_sha256
+from closedverse import settings
+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
+from django.dispatch import receiver
+import re
+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, user_agent, nick, nn, gravatar):
+ user = self.model(
+ username = username,
+ nickname = util.filterchars(nick),
+ addr = addr,
+ signup_addr = signup_addr,
+ user_agent = user_agent,
+ email = email,
+ )
+ spamuser = True
+ 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)
+ if spamuser:
+ profile.let_freedom = True
+ else:
+ profile.let_freedom = True
+ 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.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:
+ 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)
+
+class User(models.Model):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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)
+ 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)
+ # ROLE: This doesn't have anything
+ role = models.SmallIntegerField(default=0, choices=((0, 'normal'), (1, 'Bot'), (2, 'Administrator'), (3, 'Moderator'), (4, 'NO'), (5, 'Donator'), (6, 'Tester'), (7, 'Cools'), (8, 'Developer'), (9, 'SMF9-Django'), (10, 'Staff'), (11, 'GAY DOGWATER' ), ( 12, 'DUMB SNAIL' ), (13, 'Russian ADRIAN'), (14, 'Contest'), (15, 'Gamecon'), (16, 'Cedar'), ))
+ addr = models.CharField(max_length=64, null=True, blank=True)
+ signup_addr = models.CharField(max_length=64, null=True, blank=True)
+ user_agent = models.TextField(null=True, blank=True)
+ # C Tokens are things that let you make communities and shit.
+ c_tokens = models.IntegerField(default=1)
+
+ # Things that don't have to do with auth lol
+ hide_online = models.BooleanField(default=False)
+ color = ColorField(default='', null=True, blank=True)
+
+ staff = models.BooleanField(default=False)
+ #active = models.SmallIntegerField(default=1, choices=((0, 'Redirect'), (1, 'Good'), (2, 'Disabled')))
+ active = models.BooleanField(default=True)
+ warned = models.BooleanField(default=False)
+ warned_reason = models.CharField(blank=True, null=True, max_length=600)
+ 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 ColorTheme(self):
+ # if the user set a theme, display that.
+ if self.theme:
+ the_theme = self.theme
+ the_theme = the_theme.strip("#")
+ # if there's no theme set by the user, use the site's picked theme.
+ elif settings.site_wide_theme_hex:
+ the_theme = settings.site_wide_theme_hex
+ the_theme = the_theme.strip("#")
+ # If there's no theme set in settings.py, return None.
+ else:
+ the_theme = None
+ return the_theme
+ 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 set_password(self, raw_password):
+ self.password = bcrypt_sha256.using(rounds=13).hash(raw_password)
+ def check_password(self, raw_password):
+ return bcrypt_sha256.using(rounds=13).verify(raw_password, hash=self.password)
+ def profile(self, thing=None):
+ # If thing is specified, that field is retrieved
+ if thing:
+ return self.profile_set.all().values_list(thing, flat=True).first()
+ # Otherwise just get full profile
+ return self.profile_set.filter(user=self).first()
+ def gravatar(self):
+ g = util.get_gravatar(self.email)
+ if not g:
+ return settings.STATIC_URL + 'img/anonymous-mii.png'
+ return g
+ 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 has_plain_avatar(self):
+ if not self.has_mh and 'http' 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):
+ first = {
+ 1: 'tester',
+ 2: 'administrator',
+ 3: 'moderator',
+ 4: 'openverse',
+ 5: 'donator',
+ 6: 'tester',
+ 7: 'urapp',
+ 8: 'developer',
+ 9: 'pipinstalldjango',
+ 10: 'staff',
+ 11: 'kanna',
+ 12: 'verified',
+ 13: 'artcon',
+ 14: 'contest',
+ 15: 'gamecom',
+ 16: 'mp',
+ }.get(self.role, '')
+ second = {
+ 1: "Bot",
+ 2: "Administrator",
+ 3: "Moderator",
+ 4: "No",
+ 5: "Donator",
+ 6: "Tester",
+ 7: "Cool Dude",
+ 8: "Wii U still best console.",
+ 9: "Rixy Installed Django!",
+ 10: "Staff",
+ 11: "GAY DOGWATER ETC",
+ 12: "THE STUPIDEST ROLE IN THE WORLD",
+ 13: "A D R I A N",
+ 14: "Contest Winner",
+ 15: "Game Contest Winner",
+ 16: "Cedar Inc.",
+ }.get(self.role, '')
+ if first:
+ first = 'official ' + first
+ 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=50)) > self.last_login:
+ return False
+ elif (timezone.now() - timedelta(seconds=48)) > 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 = 'https://mii-secure.cdn.nintendo.net/{0}_{1}_face.png'.format(self.avatar, feeling)
+ 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.filter(by=self).count()
+ def num_posts(self):
+ return self.post_set.filter(creator=self).count()
+ def num_comments(self):
+ return self.comment_set.filter().count()
+ def num_following(self):
+ return self.follow_source.filter().count()
+ def num_followers(self):
+ return self.follow_target.filter().count()
+ def num_friends(self):
+ return self.friend_source.filter().count() + self.friend_target.filter().count()
+ def can_follow(self, user):
+ #if UserBlock.find_block(self, user):
+ # return False
+ return True
+ def can_view(self, user):
+ #if UserBlock.find_block(self, user, full=True):
+ # 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():
+ return False
+ #if source.profile('moyenne'):
+ # return False
+ return True
+ # BLOCK this user from SOURCE
+ def make_block(self, source, full=False):
+ if find_block(source, self):
+ return False
+ return UserBlock.objects.create(source=source, target=self, full=full)
+ def get_posts(self, limit=50, offset=0, request=None):
+ 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().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().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=50, offset=0, request=None):
+ 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().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().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('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 >= 2) or self.is_staff():
+ return True
+ return False
+ # Does user have authority over self?
+ def has_authority(self, user):
+ if self.is_staff():
+ return False
+ 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 not self.get_fr(source):
+ 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.filter().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(settings.STATIC_URL + 'img/menu-logo.png'),
+ 'contact': 'something',
+ 'link': request.build_absolute_uri(reverse('main:forgot-passwd')) + "?token=" + base64.urlsafe_b64encode(bytes(self.password, 'utf-8')).decode(),
+ })
+ subj = 'Closedverse password reset for "{0}"'.format(self.username)
+ return send_mail(
+ subject='Reset password',
+ html_message=htmlmsg,
+ message=htmlmsg,
+ from_email="noreply@cedar.doctor",
+ 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 Community(models.Model):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ id = models.AutoField(primary_key=True)
+ name = models.CharField(max_length=255)
+ description = models.TextField(blank=True, default='')
+ ico = models.ImageField(upload_to='icon/%y/%m/%d/', max_length=100, blank=True, null=True)
+ banner = models.ImageField(upload_to='banner/%y/%m/%d/', max_length=100, blank=True, null=True)
+ # Type: 0 - general, 1 - game, 2 - special
+ type = models.SmallIntegerField(default=0, choices=((0, 'General'), (1, 'Game'), (2, 'Special'), (3, 'User Community'), (4, 'Hide')))
+ # Platform - 0/none, 1/3DS, 2/Wii U, 3/both
+ 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, 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)
+ is_feature = models.BooleanField(default=False)
+ require_auth = models.BooleanField(default=False)
+ allowed_users = models.TextField(null=True, blank=True)
+ creator = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
+
+ objects = PostManager()
+ real = models.Manager()
+ def popularity(self):
+ popularity = Post.objects.filter(community=self).count()
+ return popularity
+ def __str__(self):
+ return self.name
+ def icon(self):
+ if self.ico and hasattr(self.ico, 'url'):
+ 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 Games",
+ 2: "Wii U Games",
+ 3: "Switch Games",
+ 4: "Wii U Games\u30FB3DS Games",
+ 5: 'PC Games',
+ 6: 'Xbox Games',
+ 7: 'Playstation Games',
+ }.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',
+ }.get(self.platform)
+ if 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
+ def get_posts(self, limit=50, offset=0, request=None, favorite=False):
+ if request.user.is_authenticated:
+ 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)).filter(community_id=self.id).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).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 post_perm(self, request):
+ if self.allowed_users:
+ allows = self.allowed_users.split(',')
+ if not request.user.is_authenticated or str(request.user.id) not in allows:
+ return False
+ return True
+ else:
+ return True
+
+ 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')):
+ return 6
+ 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
+ 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
+ # Check for spam using our OWN ALGO!!!!!!!!!
+ if request.user.has_postspam(request.POST.get('body'), upload, drawing):
+ return 7
+ new_post = self.post_set.create(body=request.POST.get('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'))
+ 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"
+
+# Links between communities for "related" communities
+class CommunityClink(models.Model):
+ # root/also order doesn't matter, time does though
+ root = models.ForeignKey(Community, related_name='one', on_delete=models.CASCADE)
+ also = models.ForeignKey(Community, related_name='two', on_delete=models.CASCADE)
+ created = models.DateTimeField(auto_now_add=True)
+ # type: related (f) / sub (t)
+ kind = models.BooleanField(default=False)
+
+ objects = CommunityFavoriteManager()
+
+# Do this, or not
+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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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='')
+ url = models.URLField(max_length=1200, null=True, blank=True, default='')
+ spoils = models.BooleanField(default=False)
+ disable_yeah = models.BooleanField(default=False)
+ created = models.DateTimeField(auto_now_add=True)
+ edited = models.DateTimeField(auto_now=True)
+ befores = models.TextField(null=True, blank=True)
+ poll = models.ForeignKey('Poll', null=True, blank=True, on_delete=models.CASCADE)
+ has_edit = models.BooleanField(default=False)
+ is_rm = models.BooleanField(default=False)
+ status = models.SmallIntegerField(default=0, choices=post_status)
+ 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 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.filter(post=self).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(post=self, by=request.user).exists()
+ else:
+ return False
+ def can_yeah(self, request):
+ if request.user.is_authenticated:
+ #if UserBlock.find_block(self.creator, request.user):
+ # return False
+ return True
+ else:
+ return False
+ def can_rm(self, request):
+ if 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, post=self)
+ def remove_yeah(self, request):
+ if not self.has_yeah(request):
+ return True
+ return self.yeah_set.filter(post=self, 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.filter(original_post=self).count()
+ def get_yeahs(self, request):
+ return Yeah.objects.filter(type=0, post=self).order_by('-created')[0:30]
+ def can_comment(self, request):
+ # TODO: Make this so that if a post's comments exceeds 100, make the user able to close the comments section
+ if self.number_comments() > 500:
+ return False
+ #if UserBlock.find_block(self.creator, request.user):
+ # return False
+ return True
+ def get_comments(self, request=None, limit=0, offset=0):
+ if request.user.is_authenticated:
+ has_yeah = Yeah.objects.filter(comment=OuterRef('id'), by=request.user.id)
+ if limit:
+ comments = self.comment_set.select_related('creator').annotate(num_yeahs=Count('yeah'), yeah_given=Exists(has_yeah)).filter(original_post=self).order_by('created')[offset:offset + limit]
+ elif offset:
+ comments = self.comment_set.select_related('creator').annotate(num_yeahs=Count('yeah'), yeah_given=Exists(has_yeah)).filter(original_post=self).order_by('created')[offset:]
+ else:
+ comments = self.comment_set.select_related('creator').annotate(num_yeahs=Count('yeah'), yeah_given=Exists(has_yeah)).filter(original_post=self).order_by('created')
+ else:
+ if limit:
+ comments = 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)[offset:offset + limit]
+ elif offset:
+ comments = 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)[offset:]
+ else:
+ comments = 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)
+ 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
+ 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
+ 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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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)
+ status = models.SmallIntegerField(default=0, choices=post_status)
+ 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.filter(comment=self, type=1).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(comment=self, type=1, by=request.user).exists()
+ else:
+ return False
+ def can_yeah(self, request):
+ if request.user.is_authenticated:
+ return True
+ else:
+ return False
+ #if UserBlock.find_block(self.creator, request.user):
+ # return False
+ def can_rm(self, request):
+ if self.creator.has_authority(request.user):
+ return True
+ #if self.original_post.is_mine(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(comment=self, type=1, 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):
+ # Todo: make this a plain int at some point
+ 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 += str(self.post.unique_id)
+ elif self.comment:
+ a += str(self.comment.unique_id)
+ return a
+
+class Profile(models.Model):
+ is_new = models.BooleanField(default=True)
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=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, choices=(
+ (0, "I don't know"), (1, "He/him"), (2, "She/her"), (3, "He/she"), (4, "It"), (5, "They/Them")
+ ))
+
+ gender_is = models.IntegerField(default=0, choices=(
+ (0, "I don't know"), (1, "Girl"), (2, "Boy"), (3, "Minion"), (4, "Toaster"), (5, "Dinosaur"), (6, "Truck"), (7, "Robot"), (8, "Monkey"), (9, "Big chungus"), (10, "Other")
+ ))
+
+ 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)
+ # 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)
+ adopted = models.ForeignKey(User, null=True, blank=True, related_name='children', on_delete=models.CASCADE)
+ # Post limit, 0 for none
+ limit_post = models.SmallIntegerField(default=0)
+ # If this is true, the user can't change their avatar or nickname
+ cannot_edit = models.BooleanField(default=False)
+ email_login = models.SmallIntegerField(default=1, choices=((0, 'Do not allow'), (1, 'Okay'), (2, 'Only allow')))
+
+ def __str__(self):
+ return "profile " + str(self.unique_id) + " 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
+ #if user.is_authenticated and UserBlock.find_block(self.user, user):
+ # return False
+ elif self.let_friendrequest == 1:
+ if not user.is_following(self.user):
+ return False
+ return True
+ 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)
+
+class Follow(models.Model):
+ # Todo: remove this
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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'),
+ ))
+ 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)
+
+ 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):
+ 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):
+ # 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
+ 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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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.filter().update(read=True)
+ def messages(self, request, limit=50, offset=0):
+ msgs = self.message_set.filter().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
+ 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):
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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):
+ # Todo: make this a plain int at some point
+ unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ 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()
+
+class RedFlag(models.Model):
+ id = models.AutoField(primary_key=True)
+ created = models.DateTimeField(auto_now_add=True)
+ post = models.ForeignKey(Post, blank=True, null=True, on_delete=models.CASCADE)
+ comment = models.ForeignKey(Comment, blank=True, null=True, on_delete=models.CASCADE)
+ user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
+ type = models.SmallIntegerField(choices=((0, 'Post'), (1, 'Comment'), (2, 'User'), ))
+ reason = models.SmallIntegerField(choices=((0, "Actual harassment"), (1, "Spam"), (2, "I don't like this"), (3, "Personal info"), (4, "Obscene use of swearing"), (5, "NSFW where not allowed"), (6, "Overly advertising/spam"), (7, "Please delete this")))
+ reasoning = models.TextField(default='', null=True, blank=True)
+
+ def __str__(self):
+ return "Report on a " + self.get_type_display() + " for " + self.get_reason_display() + ": " + str(self.reasoning)
+
+# Login attempts: for incorrect passwords
+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 ' + 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)
+# Fun
+class ThermostatTouch(models.Model):
+ id = models.AutoField(primary_key=True)
+ created = models.DateTimeField(auto_now_add=True)
+ who = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
+ lvl = models.IntegerField(default=1)
+
+ def __str__(self):
+ return str(created) + " touched the thermostat, setting it to " + str(lvl) + " degrees celsius"
+
+# 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):
+ if full:
+ return UserBlock.objects.filter(Q(source=first, full=True) & Q(target=second, full=True) | Q(target=first, full=True) & Q(source=second, full=True)).exists()
+ return UserBlock.objects.filter(Q(source=first) & Q(target=second) | Q(target=first) & Q(source=second)).exists()
+
+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, "Generate passwd reset"), (4, "User delete"), (5, "Image delete"), (6, "Purge 1"), (7, "Purge 2"), (8, "Purge 3"), (9, "Purge 4"), (10, "Purge 5"), (11, "Un-purge 1"), (12, 'Changed server settings'), ))
+ 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)
+ 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
+
+# TODO: MAKE THIS ACTUALLY WORK
+class Restriction(models.Model):
+ id = models.AutoField(primary_key=True)
+ created = models.DateTimeField(auto_now_add=True)
+ type = models.SmallIntegerField(choices=((0, "Prevent yeah"), (1, "Prevent follow"), (2, "Prevent comment"), ))
+ post = models.ForeignKey(Post, related_name='restriction_post', null=True, on_delete=models.CASCADE)
+ comment = models.ForeignKey(Comment, related_name='restriction_comment', null=True, on_delete=models.CASCADE)
+ user = models.ForeignKey(User, related_name='restriction_user', null=True, on_delete=models.CASCADE)
+ by = models.ForeignKey(User, related_name='restriction_by', on_delete=models.CASCADE)
+
+ def __str__(self):
+ return "Restrict " + str(self.user) + " on " + self.get_type_display() + " at " + str(self.created)
+
+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
+
+ 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)
+
+class Motd(models.Model):
+ id = models.AutoField(primary_key=True)
+ order = models.IntegerField(max_length=3, default=1)
+ show = models.BooleanField(default=True)
+ created = models.DateTimeField(auto_now_add=True)
+ Title = models.CharField(max_length=256, blank=True, default='Title')
+ message = models.TextField(null=False, blank=False)
+ hide_date = models.BooleanField(default=False)
+ image = models.ImageField(upload_to='MOTD/%y/%m/%d/', max_length=255, null=True, blank=True)
+
+ def get_one():
+ mesoftheday = Motd.objects.order_by('order', '-id').filter(show=True)
+ return mesoftheday
+
+ def motds_available():
+ global motdsavailable
+ if(Motd.objects.filter(show=True).exists()):
+ motdsavailable = True
+ else:
+ motdsavailable = False
+ return motdsavailable
+
+class welcomemsg(models.Model):
+ id = models.AutoField(primary_key=True)
+ order = models.IntegerField(max_length=3, default=1)
+ show = models.BooleanField(default=True)
+ created = models.DateTimeField(auto_now_add=True)
+ Title = models.CharField(max_length=256, null=False, blank=False, default='Title')
+ message = models.TextField(null=False, blank=False)
+ image = models.ImageField(upload_to='welcomemsg/%y/%m/%d/', max_length=255, null=True, blank=True)
+
+ def get_one():
+ welmes = welcomemsg.objects.order_by('order', '-id').filter(show=True)
+ return welmes
+
+ def welcomemsg_available():
+ global welcomeisavailable
+ if(welcomemsg.objects.filter(show=True).exists()):
+ welcomeisavailable = True
+ else:
+ welcomeisavailable = False
+ return welcomeisavailable
+
+# 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)
+ new_nickname = models.CharField(max_length=64, blank=True)
+ old_comment = models.TextField(blank=True)
+ new_comment = models.TextField(blank=True)
+
+ def __str__(self):
+ return str(self.user) + ' changed profile details'
+
+# 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)
diff --git a/closedverse_main/serializers.py b/closedverse_main/serializers.py
new file mode 100644
index 0000000..02fadc1
--- /dev/null
+++ b/closedverse_main/serializers.py
@@ -0,0 +1,37 @@
+import json
+class CommunitySerializer():
+ """
+ Serializes communities for use with JSON
+ """
+ @staticmethod
+ def single(community):
+ """
+ Return one dict for a community, meant to be used in community pages, etc
+ """
+ return {
+ 'id': community.id,
+ 'unique_id': str(community.unique_id),
+ 'name': community.name,
+ 'icon': (community.icon() or None),
+ 'banner': (community.banner or None),
+ 'description': community.description,
+ 'type': community.type,
+ 'platform': community.platform,
+ 'allowed_users': json.loads(community.allowed_users) if community.allowed_users else None,
+ 'creator': community.creator_id
+ }
+ @staticmethod
+ def many(queryset):
+ """
+ Return a community meant to be used in a bigger list, etc
+ """
+ return [
+ {
+ 'id': community.id,
+ 'name': community.name,
+ 'icon': (community.icon() or None),
+ 'banner': (community.banner or None),
+ 'type': community.type,
+ 'platform': community.platform
+ } for community in queryset
+ ]
\ No newline at end of file
diff --git a/closedverse_main/templates/403.html b/closedverse_main/templates/403.html
new file mode 100644
index 0000000..53e56a1
--- /dev/null
+++ b/closedverse_main/templates/403.html
@@ -0,0 +1,4 @@
+{% extends "closedverse_main/layout.html" %}
+{% load closedverse_tags %}{% block main-body %}
+{% nocontent "403 Forbidden" %}
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/404.html b/closedverse_main/templates/404.html
new file mode 100644
index 0000000..96b4cee
--- /dev/null
+++ b/closedverse_main/templates/404.html
@@ -0,0 +1,4 @@
+{% extends "closedverse_main/layout.html" %}
+{% load closedverse_tags %}{% block main-body %}
+{% nocontent "If you're not careful and you noclip out of reality in the wrong areas, you'll end up in the Backrooms, where it's nothing but the stink of old moist carpet, the madness of mono-yellow, the endless background noise of fluorescent lights at maximum hum-buzz, and approximately six hundred million square miles of randomly segmented empty rooms to be trapped in. God save you if you hear something wandering around nearby, because it sure as hell has heard you." %}
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/activity-loading.html b/closedverse_main/templates/closedverse_main/activity-loading.html
new file mode 100644
index 0000000..e7e6aba
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/activity-loading.html
@@ -0,0 +1,44 @@
+{% extends "closedverse_main/layout.html" %}
+{% load static %}{% load closedverse_user %}{% load closedverse_community %}{% load closedverse_tags %}
+{% block main-body %}
+{% user_sidebar request user user.profile 0 True %}
+
+
+
+ Activity Feed
+
+
+
+
+
+
+
+
+ {% if community %}
+
+ {% post_form request.user community %}
+
+ {% endif %}
+
+
+
+ {% discordapp_spinner %}
+
Loading activity feed...
+
+
+
+
+
The activity feed couldn't be loaded. Check your Internet connection, wait a moment, and then try reloading.
+
+
+
+
+
+
+{% endblock %}
diff --git a/closedverse_main/templates/closedverse_main/activity.html b/closedverse_main/templates/closedverse_main/activity.html
new file mode 100644
index 0000000..b9f7e13
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/activity.html
@@ -0,0 +1 @@
+{% load closedverse_user %}{% u_post_list posts next 2 %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/change-password.html b/closedverse_main/templates/closedverse_main/change-password.html
new file mode 100644
index 0000000..de81a5e
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/change-password.html
@@ -0,0 +1,31 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_user %}
+{% user_sidebar request user user.profile 0 True %}
+
+
+{% endblock %}
diff --git a/closedverse_main/templates/closedverse_main/comment-view.html b/closedverse_main/templates/closedverse_main/comment-view.html
new file mode 100644
index 0000000..8990ae0
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/comment-view.html
@@ -0,0 +1,88 @@
+{% extends "closedverse_main/layout.html" %}
+{% load closedverse_tags %}{% load closedverse_community %}{% block main-body %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/community-search.html b/closedverse_main/templates/closedverse_main/community-search.html
new file mode 100644
index 0000000..21eb658
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community-search.html
@@ -0,0 +1,44 @@
+{% extends "closedverse_main/layout.html" %}
+{% load static %}{% load closedverse_tags %}{% load closedverse_user %}
+{% block main-body %}
+{% user_sidebar request user user.profile 0 True %}
+
+
Search Communities
+
+ {% if communities %}
+
+
{{ communities|length }} result{% if not communities|length == 1 %}s{% endif %} found for "{{ query }}"
+
+
+ {% else %}
+
+
+
"{{ query }}" could not be found.
+Try searching for something different.
+
+
+ {% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/community_all.html b/closedverse_main/templates/closedverse_main/community_all.html
new file mode 100644
index 0000000..bb7deeb
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community_all.html
@@ -0,0 +1,47 @@
+{% extends "closedverse_main/layout.html" %}
+{% load closedverse_tags %}{% load closedverse_community %}{% block main-body %}
+
+
+
+
+
+
+
+ {% community_page_element general "General Communities" %}
+
+
+ {% community_page_element game "Game Communities" %}
+
+
+ {% community_page_element special "Special Communities" %}
+
+
+ {% community_page_element user_communities "User Communities" %}
+
+ {% if has_next %}
+
Next
+ {% endif %}
+ {% if has_back %}
+
 
+
Back
+ {% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/community_create.html b/closedverse_main/templates/closedverse_main/community_create.html
new file mode 100644
index 0000000..44bf6c9
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community_create.html
@@ -0,0 +1,41 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_user %}{% load closedverse_tags %}
+
+
Create a new community
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/community_favorites.html b/closedverse_main/templates/closedverse_main/community_favorites.html
new file mode 100644
index 0000000..f7b2a6c
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community_favorites.html
@@ -0,0 +1,9 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_user %}{% load closedverse_community %}
+{% user_sidebar request user profile 0 %}
+
+
{% if other %}{{ user.nickname }}'s favorite{% else %}Favorite{% endif %} communities
+{% community_page_element favorites None %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/community_list.html b/closedverse_main/templates/closedverse_main/community_list.html
new file mode 100644
index 0000000..1e278b2
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community_list.html
@@ -0,0 +1,96 @@
+{% extends "closedverse_main/layout.html" %}
+{% load closedverse_tags %}{% load closedverse_community %}{% block main-body %}
+
+
+
+
+
+ {% if favorites %}
+
+
+ {% endif %}
+ {% if feature %}
+ {% community_page_element feature "Featured Communities" True %}
+ {% endif %}
+ {% community_page_element general "General Communities" %}
+ {% community_page_element game "Game Communities" %}
+ {% community_page_element special "Special Communities" %}
+ {% community_page_element user_communities "User owned Communities" %}
+
Show more
+
+
+{% endblock %}
diff --git a/closedverse_main/templates/closedverse_main/community_tools.html b/closedverse_main/templates/closedverse_main/community_tools.html
new file mode 100644
index 0000000..a5a5562
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community_tools.html
@@ -0,0 +1,64 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_user %}{% load closedverse_tags %}{% load closedverse_community %}
+{% if community.is_feature %}
+
Who the fuck gave you ownership of a fucking featured community!
+
{% endif %}
+{% community_sidebar community request %}
+
+
Change settings for {{ community.creator }}'s community
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/community_view.html b/closedverse_main/templates/closedverse_main/community_view.html
new file mode 100644
index 0000000..e38fe41
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/community_view.html
@@ -0,0 +1,25 @@
+{% extends "closedverse_main/layout.html" %}{% block main-body %}{% load closedverse_community %}
+{% community_sidebar community request %}
+
+ {% if user.is_warned and user.is_authenticated and user.is_active %}
+
+
WARNING : You have been issued a warning by an administrator. No features have been restricted, as this is just a warning.
+
+ {% if user.get_warned_reason %}
+
Reason: "{{ user.get_warned_reason }}"
+ {% endif %}
+
+
+ {% endif %}
+
+
{{ community.name }}:
+ {% if request.user.is_authenticated and community.post_perm %}
+ {% post_form request.user community %}
+ {% endif %}
+
+ {% post_list posts next %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/comment-form.html b/closedverse_main/templates/closedverse_main/elements/comment-form.html
new file mode 100644
index 0000000..cebc445
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/comment-form.html
@@ -0,0 +1,55 @@
+{% load closedverse_community %}
+{% endif %}
+
+{% if not user.is_active %}
+Your account has been disabled.
+{% endif %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/community_page_elem.html b/closedverse_main/templates/closedverse_main/elements/community_page_elem.html
new file mode 100644
index 0000000..2071b7d
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/community_page_elem.html
@@ -0,0 +1,32 @@
+{% load closedverse_tags %}{% if title %}
+{% endif %}{% if communities %}
+
+{% else %}
+ {% if title %}{% endif %}
+ {% if title %}
+ {% nocontent "No communities of this type have been created yet." %}
+ {% else %}
+ {% nocontent "You haven't favorited any communities yet. Use the favorite button in a community to add it here." %}
+ {% endif %}
+ {% if title %}
{% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/community_post.html b/closedverse_main/templates/closedverse_main/elements/community_post.html
new file mode 100644
index 0000000..301c540
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/community_post.html
@@ -0,0 +1,97 @@
+{% if not post.is_rm %}
+{% load closedverse_tags %}
+ {% if with_community_container %}
+
+ {% endif %}
+ {% user_icon_container post.creator post.feeling %}
+ {% if post.creator == post.community.creator %}
+
Community Owner
+ {% endif %}
+
{{ post.creator.nickname }}
+ {% if not post.creator.is_active %}
+
Banned
+ {% endif %}
+
+ Spoilers·
+ {% if post.has_edit %}
+ Edited ·
+ {% endif %}
+ {% time post.created %}
+ {% if post.drawing %}
+ (handwritten)
+ {% endif %}
+
+
+ {% if post.yt_vid %}
+
+ {% endif %}
+ {% if post.drawing %}
+
+ {% else %}
+ {% if post.has_line_trun %}
+
{{ post.body|truncatechars:200 }}
+ {% else %}
+
{{ post.body|truncatechars:200|linebreaksbr }}
+ {% endif %}
+ {% endif %}
+ {% if post.screenshot %}
{% endif %}
+ {% if post.spoils and post.creator.is_active %}
+
This post contains spoilers.
+
View Post
+
+ {% endif %}
+ {% if not post.creator.is_active %}
+
Content hidden because its creator was banned.
+ {% if post.creator.get_warned_reason %}
+
Reason: {{post.creator.get_warned_reason}}
+ {% endif %}
+ View Anyway
+
+ {% endif %}
+
+
{% empathy_txt post.feeling post.has_yeah %}
+ {% if post.url %}
+
+ {% endif %}
+
Yeahs {{ post.number_yeahs }}
+ {% if not post.original_post %}
+
Comments {{ post.number_comments }}
+ {% endif %}
+
+{% if post.recent_comment and post.recent_comment.creator.is_active %}
+
+{% if post.number_comments > 1 %}
+
+ View all comments ({{ post.number_comments }})
+
+{% endif %}
+
+
+{% endif %}
+
+
+{% endif %}
diff --git a/closedverse_main/templates/closedverse_main/elements/community_sidebar.html b/closedverse_main/templates/closedverse_main/elements/community_sidebar.html
new file mode 100644
index 0000000..7c8c385
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/community_sidebar.html
@@ -0,0 +1,46 @@
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/discordapp-spinner.html b/closedverse_main/templates/closedverse_main/elements/discordapp-spinner.html
new file mode 100644
index 0000000..53d1e9c
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/discordapp-spinner.html
@@ -0,0 +1 @@
+I'm loading it !
diff --git a/closedverse_main/templates/closedverse_main/elements/empathy-content.html b/closedverse_main/templates/closedverse_main/elements/empathy-content.html
new file mode 100644
index 0000000..56270d5
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/empathy-content.html
@@ -0,0 +1,6 @@
+{% load closedverse_tags %}
+{% if myself.is_authenticated %}
{% endif %}
+{% for yeah in yeahs %}{% if not myself.is_authenticated or not yeah.by == myself %}
+
+{% endif %}{% endfor %}
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/feeling-selector.html b/closedverse_main/templates/closedverse_main/elements/feeling-selector.html
new file mode 100644
index 0000000..a5d4ec4
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/feeling-selector.html
@@ -0,0 +1,2 @@
+normal happy like surprised frustrated puzzled
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/file-button.html b/closedverse_main/templates/closedverse_main/elements/file-button.html
new file mode 100644
index 0000000..662820d
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/file-button.html
@@ -0,0 +1,16 @@
+
+
+
+ Image PNG, JPG, JPEG and GIF are allowed.
+ Upload
+
+
+
+
+
+
+
diff --git a/closedverse_main/templates/closedverse_main/elements/fr-accept.html b/closedverse_main/templates/closedverse_main/elements/fr-accept.html
new file mode 100644
index 0000000..28ee8e2
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/fr-accept.html
@@ -0,0 +1,15 @@
+{% load closedverse_tags %}{% load closedverse_user %}
+
+
+
Friend Request from {{ fr.source.nickname }} at {% time fr.created True %}
+
+ {% user_sidebar_info fr.source %}
+
{% if fr.body %}{{ fr.body }}{% else %}(No message) {% endif %}
+
Accept {{ fr.source.nickname }}'s friend request?
+
+ Cancel Reject Accept
+
+
+
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/memo-drawboard.html b/closedverse_main/templates/closedverse_main/elements/memo-drawboard.html
new file mode 100644
index 0000000..e5d481b
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/memo-drawboard.html
@@ -0,0 +1,28 @@
+
+
+
+
Drawing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/message-form.html b/closedverse_main/templates/closedverse_main/elements/message-form.html
new file mode 100644
index 0000000..ece510a
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/message-form.html
@@ -0,0 +1,41 @@
+{% load closedverse_community %}
+{% endif %}
+{% if not user.is_active %}
+Your account has been disabled.
+{% endif %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/message-list.html b/closedverse_main/templates/closedverse_main/elements/message-list.html
new file mode 100644
index 0000000..6e25e83
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/message-list.html
@@ -0,0 +1,7 @@
+{% load closedverse_user %}
+
+ {% for post in messages %}
+ {% message post %}
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/message.html b/closedverse_main/templates/closedverse_main/elements/message.html
new file mode 100644
index 0000000..375a97c
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/message.html
@@ -0,0 +1,17 @@
+{% load markdown_deux_tags %}{% load closedverse_tags %}
+ {% user_icon_container message.creator message.feeling %}
+
+ {% time message.created %}{% if message.read %} - Read{% endif %}
+ Delete
+
+
+ {% if message.drawing %}
+
+ {% else %}
+
{{ message.body|markdown }}
+ {% endif %}
+ {% if message.screenshot %}
+
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/names.html b/closedverse_main/templates/closedverse_main/elements/names.html
new file mode 100644
index 0000000..baaa2ee
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/names.html
@@ -0,0 +1 @@
+{% if names|length == 1 %}{{ names.0.nickname }} {% elif names|length == 2 %}{{ names.0.nickname }} and {{ names.1.nickname }} {% elif names|length == 3 %}{{ names.0.nickname }} , {{ names.1.nickname }} , and {{ names.2.nickname }} {% elif names|length == 4 %}{{ names.0.nickname }} , {{ names.1.nickname }} , {{ names.2.nickname }} , and {{ names.3.nickname }} {% elif names|length == 5 %}{{ names.0.nickname }} , {{ names.1.nickname }} , {{ names.2.nickname }} , {{ names.3.nickname }} , and 1 other{% else %}{{ names.0.nickname }} , {{ names.1.nickname }} , {{ names.2.nickname }} , {{ names.3.nickname }} , and {{ nameallmn }} others{% endif %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/no-content.html b/closedverse_main/templates/closedverse_main/elements/no-content.html
new file mode 100644
index 0000000..2e15121
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/no-content.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/p_username.html b/closedverse_main/templates/closedverse_main/elements/p_username.html
new file mode 100644
index 0000000..778c597
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/p_username.html
@@ -0,0 +1 @@
+{{ user.nickname }} {{ user.username }}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/post-comment.html b/closedverse_main/templates/closedverse_main/elements/post-comment.html
new file mode 100644
index 0000000..47d5801
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/post-comment.html
@@ -0,0 +1,51 @@
+{% if not comment.is_rm %}{% load closedverse_tags %}
+{% endif %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/post-form.html b/closedverse_main/templates/closedverse_main/elements/post-form.html
new file mode 100644
index 0000000..f5cbae8
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/post-form.html
@@ -0,0 +1,69 @@
+{% load closedverse_community %}
+{% endif %}
+
+{% if not user.is_active %}
+Your account has been disabled.
+{% endif %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/post-list.html b/closedverse_main/templates/closedverse_main/elements/post-list.html
new file mode 100644
index 0000000..520090b
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/post-list.html
@@ -0,0 +1,9 @@
+{% load closedverse_tags %}{% load closedverse_community %}
+{% if posts %}
+{% for post in posts %}
+{% community_post post type %}
+{% endfor %}
+{% else %}
+{% if nf %}{% nocontent nf %}{% else %}{% nocontent "The posts couldn't be loaded." %}{% endif %}
+{% endif %}
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/post_comments.html b/closedverse_main/templates/closedverse_main/elements/post_comments.html
new file mode 100644
index 0000000..97dc1f0
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/post_comments.html
@@ -0,0 +1,5 @@
+{% load closedverse_community %}
+{% for comment in comments %}
+{% post_comment comment %}
+{% endfor %}
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/profile-post.html b/closedverse_main/templates/closedverse_main/elements/profile-post.html
new file mode 100644
index 0000000..a1c6e40
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/profile-post.html
@@ -0,0 +1,64 @@
+{% load closedverse_tags %}
+
+
+
+
+
+ {% user_icon_container post.creator post.feeling %}
+
+
{{ post.creator.nickname }}
+
+
+ {% if post.spoils %}
+ Spoilers ·
+ {% endif %}
+ {% if post.has_edit %}
+ Edited ({% time post.edited %}) ·
+ {% endif %}
+ {% time post.created %}
+ {% if post.drawing %}
+ (handwritten)
+ {% endif %}
+
+ {% if post.yt_vid %}
+
+ {% endif %}
+ {% if post.screenshot %}
+
+ {% endif %}
+ {% if post.drawing %}
+
+ {% else %}
+ {% if post.has_line_trun %}
+
{{ post.body|truncatechars:100 }}
+ {% else %}
+
{{ post.body|truncatechars:100|linebreaksbr }}
+ {% endif %}
+ {% endif %}
+ {% if post.spoils and post.creator.is_active %}
+
This post contains spoilers.
+ View Post
+
+ {% endif %}
+ {% if not post.creator.is_active %}
+
Content hidden because its creator was banned.
+ {% if post.creator.get_warned_reason %}
+
Reason: {{post.creator.get_warned_reason}}
+ {% endif %}
+ View Anyway
+
+ {% endif %}
+
+
{% empathy_txt post.feeling post.has_yeah %}
+ {% if post.url %}
+
+ {% endif %}
+
Yeahs {{ post.number_yeahs }}
+
Comments {{ post.number_comments }}
+
+
+
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/profile-user-list.html b/closedverse_main/templates/closedverse_main/elements/profile-user-list.html
new file mode 100644
index 0000000..45a2a0c
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/profile-user-list.html
@@ -0,0 +1,7 @@
+{% load closedverse_user %}
+
+ {% for user in users %}
+ {% profile_user user request %}
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/profile-user.html b/closedverse_main/templates/closedverse_main/elements/profile-user.html
new file mode 100644
index 0000000..9949170
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/profile-user.html
@@ -0,0 +1,24 @@
+{% load closedverse_tags %}
+ {% user_icon_container user %}
+
+{% if request.user.is_authenticated and not user.is_following %}
+
+ Follow
+ Follow
+
+{% endif %}
+
+
+
+ {{ user.nickname }}
+ {{ user.username }}
+
+
{{ user.profile.comment }}
+
+ {% if user.profile.favorite.screenshot %}
+
+
+
+ {% endif %}
+
+
diff --git a/closedverse_main/templates/closedverse_main/elements/u-post-list.html b/closedverse_main/templates/closedverse_main/elements/u-post-list.html
new file mode 100644
index 0000000..7300461
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/u-post-list.html
@@ -0,0 +1,9 @@
+{% load closedverse_tags %}{% load closedverse_user %}
+{% if posts %}
+{% for post in posts %}
+{% user_post post type %}
+{% endfor %}
+{% else %}
+{% if nf %}{% nocontent nf %}{% else %}{% nocontent "The posts couldn't be loaded." %}{% endif %}
+{% endif %}
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/user-icon-container.html b/closedverse_main/templates/closedverse_main/elements/user-icon-container.html
new file mode 100644
index 0000000..9172bc1
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/user-icon-container.html
@@ -0,0 +1 @@
+
diff --git a/closedverse_main/templates/closedverse_main/elements/user-notification.html b/closedverse_main/templates/closedverse_main/elements/user-notification.html
new file mode 100644
index 0000000..7e6a790
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/user-notification.html
@@ -0,0 +1,27 @@
+{% load closedverse_tags %}
+ {% user_icon_container notification.source %}
+
+ {% if notification.type == 0 %}
+ {% print_names notification.all_users %} gave
your post ({{ notification.context_post.trun|truncatechars:30 }}) a Yeah.
+ {% elif notification.type == 1 %}
+ {% print_names notification.all_users %} gave
your comment ({{ notification.context_comment.trun|truncatechars:30 }}) a Yeah.
+ {% elif notification.type == 2 %}
+ {% print_names notification.all_users %} commented on
your post ({{ notification.context_post.trun|truncatechars:30 }}) .
+ {% elif notification.type == 3 %}
+ {% print_names notification.all_users %} commented on
{{ notification.source.nickname }}'s post ({{ notification.context_post.trun|truncatechars:30 }}) .
+ {% elif notification.type == 4 %}
+ Followed by {% print_names notification.all_users %}.
+ {% endif %}
+
{% time notification.latest %}
+{% if notification.type == 4 and not notification.source.is_following and not notification.all_users|length > 1 %}
+
+ Follow
+ Follow
+
+{% else %}
+
+{% endif %}
+
+
+
+
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/elements/user-sidebar-info.html b/closedverse_main/templates/closedverse_main/elements/user-sidebar-info.html
new file mode 100644
index 0000000..d32c5ed
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/user-sidebar-info.html
@@ -0,0 +1,11 @@
+{% load closedverse_tags %}
+
+ {% if user.get_class.1 %}
{% user_level user %}
{% endif %}
+
+
{{ user.nickname }}
+
{{ user.username }}
+
diff --git a/closedverse_main/templates/closedverse_main/elements/user-sidebar.html b/closedverse_main/templates/closedverse_main/elements/user-sidebar.html
new file mode 100644
index 0000000..8df1dd8
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/elements/user-sidebar.html
@@ -0,0 +1,232 @@
+{% load closedverse_tags %}{% load closedverse_user %}
+{% if user.is_authenticated %}
+{% endif %}
diff --git a/closedverse_main/templates/closedverse_main/forgot_page.html b/closedverse_main/templates/closedverse_main/forgot_page.html
new file mode 100644
index 0000000..c740bba
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/forgot_page.html
@@ -0,0 +1,20 @@
+{% extends "closedverse_main/layout.html" %}
+{% load static %}{% block main-body %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/forgot_reset.html b/closedverse_main/templates/closedverse_main/forgot_reset.html
new file mode 100644
index 0000000..00dd635
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/forgot_reset.html
@@ -0,0 +1,22 @@
+{% extends "closedverse_main/layout.html" %}
+{% load static %}{% block main-body %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/friendrequests.html b/closedverse_main/templates/closedverse_main/friendrequests.html
new file mode 100644
index 0000000..1c6f305
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/friendrequests.html
@@ -0,0 +1,45 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_tags %}{% load closedverse_user %}
+{% user_sidebar request user user.profile 0 True %}
+
+
+
+
{{ title }}
+
+
+ {% for fr in friendrequests %}
+ {% fr_accept fr %}
+ {% endfor %}
+
+ {% if friendrequests %}
+ {% for fr in friendrequests %}
+
+ {% user_icon_container fr.source %}
+
+
{{ fr.source.nickname }} {% time fr.created %}
+
View friend request
+
+
+ {% endfor %}
+ {% else %}
+ {% nocontent "No friend requests yet." %}
+ {% endif %}
+
+
+
+
+{% endblock %}
diff --git a/closedverse_main/templates/closedverse_main/help/Active_clones.html b/closedverse_main/templates/closedverse_main/help/Active_clones.html
new file mode 100644
index 0000000..fc39824
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/help/Active_clones.html
@@ -0,0 +1,98 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_user %}
+{% user_sidebar request user user.profile 0 True %}
+
+
+
+
+
Notice : Your IP and email are both viewable in MySQL. Use caution when using clones that are hosted by untrusted users.
+
+
+
+
Active clones
+
+
+
im too lazy to update this
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/closedverse_main/templates/closedverse_main/help/contact.html b/closedverse_main/templates/closedverse_main/help/contact.html
new file mode 100644
index 0000000..95251af
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/help/contact.html
@@ -0,0 +1,14 @@
+{% extends "closedverse_main/layout.html" %}
+{% block main-body %}{% load closedverse_user %}
+{% user_sidebar request user user.profile 0 True %}
+
+
+
Contact the team behind this
+
+
+
I'll make this page later, need permission from our admins and shit...
+
+
+
+
+{% endblock %}
diff --git a/closedverse_main/templates/closedverse_main/help/email.html b/closedverse_main/templates/closedverse_main/help/email.html
new file mode 100644
index 0000000..448bb4e
--- /dev/null
+++ b/closedverse_main/templates/closedverse_main/help/email.html
@@ -0,0 +1,15 @@
+
+
+ Hi
+
+
+
+
+
+
+ A password reset request has been submitted for your email.
+ Follow this link to reset your password: {{ link }}
+ If you didn't submit this, please ignore this email, someone probably tried to use your email to reset, and they can't have any access unless they actually have your email address. If you're having trouble, contact us here .
+ Thank you for using our service! :)
+
+
{{ profile.comment|truncatechars:300|linebreaksbr|urlize }}
+ {% if profile.comment|length > 299 %} +{{ profile.comment|linebreaksbr }}
+ + {% endif %} +