first commit
This commit is contained in:
parent
0761828631
commit
5b7d018750
71
.gitignore
vendored
Normal file
71
.gitignore
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
media/2024-11-23 11-49-Rowan.pcmp
|
||||||
|
media/2024-11-23 18-11-Gabriella.pcmp
|
||||||
|
media/2024-11-24 09-14-Default.pcmp
|
||||||
|
media/2024-11-24 11-04-Gabriella.pcmp
|
||||||
|
media/2024-11-24 19-44-Rowan.pcmp
|
||||||
|
media/2025-09-06 17-56-Gabriella.pcmp
|
||||||
|
media/2025-09-06 19-06-Rowan.pcmp
|
||||||
|
instance/db.sqlite
|
||||||
|
static/avatars/0d1c5f6dbb1d44c59f43c7d10cd3f9d3_l.png
|
||||||
|
static/avatars/0d1c5f6dbb1d44c59f43c7d10cd3f9d3_m.png
|
||||||
|
static/avatars/0d1c5f6dbb1d44c59f43c7d10cd3f9d3_s.png
|
||||||
|
static/avatars/1c1e283bc3974a338306df55809c3eb9_l.png
|
||||||
|
static/avatars/1c1e283bc3974a338306df55809c3eb9_m.png
|
||||||
|
static/avatars/1c1e283bc3974a338306df55809c3eb9_s.png
|
||||||
|
static/avatars/3e9717b8a2484c36801c983a7c98389e_raw.png
|
||||||
|
static/avatars/6f9d062c23774444bf97b30768aa7e3f_raw.png
|
||||||
|
static/avatars/8d7ae602f4e744e2b0de0cad75280c4b_l.png
|
||||||
|
static/avatars/8d7ae602f4e744e2b0de0cad75280c4b_m.png
|
||||||
|
static/avatars/8d7ae602f4e744e2b0de0cad75280c4b_s.png
|
||||||
|
static/avatars/8de5d46847d243b7946fb987c1d7a158_raw.png
|
||||||
|
static/avatars/9d7056cd58604367b3da797d47bf7de3_raw.png
|
||||||
|
static/avatars/9df04f769ebd4b8298198abbbd4696ee_l.png
|
||||||
|
static/avatars/9df04f769ebd4b8298198abbbd4696ee_m.png
|
||||||
|
static/avatars/9df04f769ebd4b8298198abbbd4696ee_s.png
|
||||||
|
static/avatars/12a6ce4e6eb9455c9fd300bc5bdcc68f_raw.png
|
||||||
|
static/avatars/13d09f763cf5423c93c476475e6fedb5_raw.png
|
||||||
|
static/avatars/39fb0118e7c247abbe5104ad039332c6_raw.png
|
||||||
|
static/avatars/071c522d704d42eebad3114439d01548_l.png
|
||||||
|
static/avatars/071c522d704d42eebad3114439d01548_m.png
|
||||||
|
static/avatars/071c522d704d42eebad3114439d01548_s.png
|
||||||
|
static/avatars/79ccc4d8993b4d7297f9ec0e01245fa4_raw.png
|
||||||
|
static/avatars/613fc5aac72c42c9b9f034d039098787_l.png
|
||||||
|
static/avatars/613fc5aac72c42c9b9f034d039098787_m.png
|
||||||
|
static/avatars/613fc5aac72c42c9b9f034d039098787_s.png
|
||||||
|
static/avatars/684ed5cda7bd4eb08decee8d3f5ceb33_raw.png
|
||||||
|
static/avatars/747c34da7f59464fa1e19856c6263c6b_l.png
|
||||||
|
static/avatars/747c34da7f59464fa1e19856c6263c6b_m.png
|
||||||
|
static/avatars/747c34da7f59464fa1e19856c6263c6b_s.png
|
||||||
|
static/avatars/956ff2dbbc8b485d9a4b2a509d67b195_raw.png
|
||||||
|
static/avatars/5041d223f89940e3ad0546baed22f99c_raw.png
|
||||||
|
static/avatars/9037d37a43f54163b0322a9c4b6e335a_l.png
|
||||||
|
static/avatars/9037d37a43f54163b0322a9c4b6e335a_m.png
|
||||||
|
static/avatars/9037d37a43f54163b0322a9c4b6e335a_s.png
|
||||||
|
static/avatars/9139f11ea5854a6bb86d5a9f38e07ac4_raw.png
|
||||||
|
static/avatars/aed55274f1c84b52bab1b30ab3adfdb2_l.png
|
||||||
|
static/avatars/aed55274f1c84b52bab1b30ab3adfdb2_m.png
|
||||||
|
static/avatars/aed55274f1c84b52bab1b30ab3adfdb2_s.png
|
||||||
|
static/avatars/ba03d8c967ef458b9069b9367f9a7a63_raw.png
|
||||||
|
static/avatars/ba72f533f5c94dd79dde64ee5aca4fa1_l.png
|
||||||
|
static/avatars/ba72f533f5c94dd79dde64ee5aca4fa1_m.png
|
||||||
|
static/avatars/ba72f533f5c94dd79dde64ee5aca4fa1_s.png
|
||||||
|
static/avatars/ba79862152174438aff256bdab9aacc1_l.png
|
||||||
|
static/avatars/ba79862152174438aff256bdab9aacc1_m.png
|
||||||
|
static/avatars/ba79862152174438aff256bdab9aacc1_s.png
|
||||||
|
static/avatars/be2d767474e3406ba2463acd65f03158_l.png
|
||||||
|
static/avatars/be2d767474e3406ba2463acd65f03158_m.png
|
||||||
|
static/avatars/be2d767474e3406ba2463acd65f03158_s.png
|
||||||
|
static/avatars/c7b11bc3d7fd4993ad03b5e9046986c4_raw.png
|
||||||
|
static/avatars/c24f0d67bb9f4ef99fe7eac3e135d8e7_l.png
|
||||||
|
static/avatars/c24f0d67bb9f4ef99fe7eac3e135d8e7_m.png
|
||||||
|
static/avatars/c24f0d67bb9f4ef99fe7eac3e135d8e7_s.png
|
||||||
|
static/avatars/c9325959a1e940c184e4e597687211ae_l.png
|
||||||
|
static/avatars/c9325959a1e940c184e4e597687211ae_m.png
|
||||||
|
static/avatars/c9325959a1e940c184e4e597687211ae_s.png
|
||||||
|
static/avatars/de8bb1c2465345f3bc6a05c560ff743d_raw.png
|
||||||
|
static/avatars/e95939dcb19544a3aab8a88e806a09b1_raw.png
|
||||||
|
static/avatars/f6b5d00609a74f7980b0e15958783da3_raw.png
|
||||||
|
static/images/2024-03-03 225803255514-gen-image-HJpDVk.jpg
|
||||||
|
static/images/Gabbie Picture 2.png
|
||||||
|
static/versions/alpha_v1.0.zip
|
||||||
|
static/versions/latest.zip
|
||||||
18
database.py
Normal file
18
database.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import easySQL, pathlib
|
||||||
|
|
||||||
|
@easySQL.Table
|
||||||
|
class Uploads():
|
||||||
|
def __init__(self):
|
||||||
|
self.name = "logins"
|
||||||
|
self.columns = {
|
||||||
|
"userid": easySQL.INTEGER,
|
||||||
|
"filename": easySQL.STRING,
|
||||||
|
"author": easySQL.STRING,
|
||||||
|
"version": easySQL.STRING,
|
||||||
|
"path": easySQL.STRING
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_upload(table, userid, filename, author, version, path):
|
||||||
|
easySQL.insert_into_table(table,
|
||||||
|
[userid, filename, author, version, path]
|
||||||
|
)
|
||||||
243
easySQL.py
Normal file
243
easySQL.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
from typing import Any
|
||||||
|
import sqlite3, pathlib
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
STRING = 'string'
|
||||||
|
INTEGER = 'integer'
|
||||||
|
UNIQUE = 'UNIQUE'
|
||||||
|
JSON = 'string' # TODO: create a function for converting lists and dict into json string and back
|
||||||
|
database = None
|
||||||
|
|
||||||
|
# TODO: add functionality to seek all entries in a filter that CONTAINS a string
|
||||||
|
|
||||||
|
|
||||||
|
def VALDATED_STRING():
|
||||||
|
return 'string'
|
||||||
|
|
||||||
|
def intergrate(database_path: pathlib.Path = None) -> sqlite3.Connection:
|
||||||
|
|
||||||
|
if not database_path:
|
||||||
|
database_path = pathlib.Path("test.sqlite")
|
||||||
|
|
||||||
|
global database
|
||||||
|
database = sqlite3.connect(database=database_path.absolute())
|
||||||
|
return database
|
||||||
|
|
||||||
|
|
||||||
|
def Table(cls):
|
||||||
|
""" easySQL decorator for table classes for easy instantiation of many of the SQL_execute strings.
|
||||||
|
|
||||||
|
This class will always need these variables defined within its __init__ method;
|
||||||
|
|
||||||
|
self.name = "foo"; This will be the name of the table in the integrated database
|
||||||
|
self.columns = {foo: dah, ...}; dictionary of foo being the column name, and dah being the columns type in the database
|
||||||
|
types for a column are:
|
||||||
|
- STRING
|
||||||
|
- INTEGER
|
||||||
|
- UNIQuE
|
||||||
|
- JSON
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Table: returns a Table class wrapped around the original class.
|
||||||
|
"""
|
||||||
|
class Table(cls):
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
super(Table, self).__init__(*args, **kwargs)
|
||||||
|
self.data_object = namedtuple(f"{self.name}_row", list(self.columns.keys()))
|
||||||
|
self.columns_validation = len(self.columns)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__} ('{self.name}')"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def create_table(self):
|
||||||
|
def manufacture_create_SQL_string() -> str:
|
||||||
|
""" Takes the super()'s columns dictionary and bulds parts of the SQL_execute string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: middle of create table SQL_execute string.
|
||||||
|
"""
|
||||||
|
# TODO: very crude way of doing it, research a better way.
|
||||||
|
middle_string = 'id integer PRIMARY KEY, '
|
||||||
|
current_count = 0
|
||||||
|
for column_name, column_type in self.columns.items():
|
||||||
|
if current_count == len(self.columns.items())-1:
|
||||||
|
middle_string += f"{column_name} {column_type}"
|
||||||
|
else:
|
||||||
|
middle_string += f"{column_name} {column_type}, "
|
||||||
|
current_count += 1
|
||||||
|
return middle_string
|
||||||
|
return f"CREATE TABLE {self.name} ({manufacture_create_SQL_string()});"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def drop_table(self):
|
||||||
|
return f"DROP TABLE {self.name};"
|
||||||
|
|
||||||
|
def select_row(self, column: str = None, match = None):
|
||||||
|
if column:
|
||||||
|
return f"SELECT * FROM {self.name} WHERE {column}= '{match}'"
|
||||||
|
else:
|
||||||
|
return f"SELECT * FROM {self.name}"
|
||||||
|
|
||||||
|
def insert_row(self, data) -> namedtuple:
|
||||||
|
|
||||||
|
def manufacture_insert_SQL_String():
|
||||||
|
middle_string = '('
|
||||||
|
gavel_string = '('
|
||||||
|
current_count = 0
|
||||||
|
for column_name in self.columns.keys():
|
||||||
|
if current_count == len(self.columns.items())-1:
|
||||||
|
middle_string += f"{column_name})"
|
||||||
|
gavel_string += f"?)"
|
||||||
|
else:
|
||||||
|
middle_string += f"{column_name}, "
|
||||||
|
gavel_string += f"?, "
|
||||||
|
current_count += 1
|
||||||
|
return f"{middle_string} VALUES {gavel_string}"
|
||||||
|
|
||||||
|
query = namedtuple('Query', ['query', 'data'])
|
||||||
|
if len(data) == self.columns_validation:
|
||||||
|
return query(query=f"INSERT INTO {self.name}{manufacture_insert_SQL_String()}", data=data)
|
||||||
|
else:
|
||||||
|
return query(query=False, data= f"passed data to {self.name} is not the right length of entries")
|
||||||
|
|
||||||
|
def update_row_by_id(self, data: dict, id: str):
|
||||||
|
""" Update a row at {id} with {data}.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): key = column, value = data to update to
|
||||||
|
id (str): row_id in Table
|
||||||
|
"""
|
||||||
|
def manufactur_update_SQL_string(data: dict) -> str:
|
||||||
|
""" takes data and builds a SQL_execute string segment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): Key = column, value = data to update to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
_type_: middle segment of SQL_execute string
|
||||||
|
"""
|
||||||
|
# TODO: this is a very crude implementtion
|
||||||
|
middle_string = ''
|
||||||
|
current_count = 0
|
||||||
|
for key, value in data.items():
|
||||||
|
if current_count == len(data.items())-1:
|
||||||
|
middle_string += f" {key} = '{value}'"
|
||||||
|
else:
|
||||||
|
middle_string += f" {key} = '{value}', "
|
||||||
|
current_count += 1
|
||||||
|
return middle_string
|
||||||
|
return f"UPDATE {self.name} SET{manufactur_update_SQL_string(data)} WHERE id = {id}"
|
||||||
|
|
||||||
|
def convert_data(self, rows: list or tuple):
|
||||||
|
""" Takes rows returned by the tables SQL_select string and returns them as namedtuples.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rows (listortuple):
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(listortuple): returns a list of namedtuple.
|
||||||
|
"""
|
||||||
|
self.keys = list(self.columns.keys())
|
||||||
|
if isinstance(rows, list):
|
||||||
|
return [self.data_object(**{key: data[i+1] for i, key in enumerate(self.keys)}) for data in rows]
|
||||||
|
if isinstance(rows, tuple):
|
||||||
|
return [self.data_object(**{key: rows[i+1] for i, key in enumerate(self.keys)})][0]
|
||||||
|
|
||||||
|
|
||||||
|
return Table
|
||||||
|
|
||||||
|
|
||||||
|
def basic_query(query: str):
|
||||||
|
""" Used as single query functions, ex. creating tables, dropping tables, updating rows
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): SQL_execute string
|
||||||
|
"""
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
|
||||||
|
def create_table(table: Table, drop=False):
|
||||||
|
|
||||||
|
if drop:
|
||||||
|
drop_table(table=table)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(table.create_table)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def drop_table(table: Table):
|
||||||
|
try:
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(table.drop_table)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def update_table_row_by_id(table: Table, row):
|
||||||
|
query = table.update_row_by_id(data=row[1], id=row[0])
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_into_table(table, data):
|
||||||
|
""" Passing a query and its data as a namedtuple will insert the query into the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (namedtuple): (query.query = SQL_execute string, query.data = tuple of column's data)
|
||||||
|
"""
|
||||||
|
query = table.insert_row(data)
|
||||||
|
assert query.query, query.data
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(query.query, query.data)
|
||||||
|
|
||||||
|
def fetchone_from_table(table: Table, filter: tuple([str, Any]) = None, convert_data=True) -> tuple:
|
||||||
|
if filter:
|
||||||
|
query = table.select_row(column=filter[0], match=filter[1])
|
||||||
|
else:
|
||||||
|
query = table.select_row()
|
||||||
|
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
|
||||||
|
if not convert_data:
|
||||||
|
return cursor.fetchone()
|
||||||
|
|
||||||
|
return table.convert_data(cursor.fetchone())
|
||||||
|
|
||||||
|
|
||||||
|
def fetchall_from_table(table, filter: tuple([str, Any]) = None, convert_data=True) -> list:
|
||||||
|
""" Fetches all rows from the database using passed query
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): SQL_execute string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: list of rows as tuples
|
||||||
|
"""
|
||||||
|
|
||||||
|
if filter:
|
||||||
|
query = table.select_row(column=filter[0], match=filter[1])
|
||||||
|
else:
|
||||||
|
query = table.select_row()
|
||||||
|
|
||||||
|
with database:
|
||||||
|
cursor = database.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
batch = cursor.fetchall()
|
||||||
|
|
||||||
|
if len(batch) == 1:
|
||||||
|
batch = batch[0]
|
||||||
|
|
||||||
|
if not convert_data:
|
||||||
|
return batch
|
||||||
|
|
||||||
|
return table.convert_data(batch)
|
||||||
483
main.py
Normal file
483
main.py
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
from flask import Flask, Request, render_template, request, redirect, url_for, flash, send_file, send_from_directory, Response, session, jsonify, current_app
|
||||||
|
from flask_avatars import Avatars
|
||||||
|
import os, pathlib, io
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from sqlalchemy.ext.mutable import MutableList, MutableDict
|
||||||
|
from flask_login import LoginManager, current_user, login_user, logout_user, UserMixin, login_required
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
import random, string, bcrypt, datetime
|
||||||
|
from copy import deepcopy
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from PIL import Image
|
||||||
|
import base64, json, pathlib
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
avatars = Avatars(app)
|
||||||
|
|
||||||
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite"
|
||||||
|
app.config["SECRET_KEY"] = "MYSUPERCOOLAPPKEY%#!%#!GJDAJVNEIALDOFJGNA"
|
||||||
|
app.config["UPLOAD_FOLDER"] = "media"
|
||||||
|
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt'
|
||||||
|
app.config['SECURITY_PASSWORD_SALT'] = b'$2b$12$wqKlYjmOfXPghx3FuC3Pu'
|
||||||
|
app.config['AVATARS_SAVE_PATH'] = 'static/avatars/'
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Users(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(250), unique=True, nullable=False)
|
||||||
|
email = db.Column(db.String(250), unique=True, nullable=False)
|
||||||
|
password = db.Column(db.String(250), nullable=False)
|
||||||
|
firstname = db.Column(db.String(250), nullable=True)
|
||||||
|
lastname = db.Column(db.String(250), nullable=True)
|
||||||
|
friendcode = db.Column(db.String(250), nullable=False)
|
||||||
|
subs = db.Column(MutableList.as_mutable(db.JSON), nullable=False)
|
||||||
|
latest = db.Column(db.Integer, nullable=True)
|
||||||
|
raw_avatar = db.Column(db.String(250), nullable=False)
|
||||||
|
s_avatar = db.Column(db.String(250), nullable=False)
|
||||||
|
m_avatar = db.Column(db.String(250), nullable=False)
|
||||||
|
l_avatar = db.Column(db.String(250), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Uploads(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
userid = db.Column(db.Integer, nullable=False)
|
||||||
|
upload_date = db.Column(db.String(250), nullable=False)
|
||||||
|
filename = db.Column(db.String(250), nullable=False)
|
||||||
|
author = db.Column(db.String(250), nullable=False)
|
||||||
|
version = db.Column(db.String(250), nullable=False)
|
||||||
|
description = db.Column(db.String(250), nullable=True)
|
||||||
|
meta_data = db.Column(MutableDict.as_mutable(db.JSON), nullable=True)
|
||||||
|
collection_json = db.Column(MutableDict.as_mutable(db.JSON), nullable=True)
|
||||||
|
character_links = db.Column(MutableDict.as_mutable(db.JSON), nullable=True)
|
||||||
|
mod_list = db.Column(MutableList.as_mutable(db.JSON), nullable=True)
|
||||||
|
path = db.Column(db.String(250), nullable=False)
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
def friend_code(length):
|
||||||
|
letters = string.ascii_letters
|
||||||
|
return ''.join(random.choice(letters) for i in range(length))
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def loader_user(user_id):
|
||||||
|
return Users.query.get(user_id)
|
||||||
|
|
||||||
|
def password_hash(input_password):
|
||||||
|
bytes = input_password.encode('utf-8')
|
||||||
|
salt = bcrypt.gensalt()
|
||||||
|
|
||||||
|
return bcrypt.hashpw(bytes, salt)
|
||||||
|
|
||||||
|
def check_password(input_password, hash):
|
||||||
|
bytes = input_password.encode('utf-8')
|
||||||
|
result = bcrypt.checkpw(bytes, hash)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login_app/avatar", methods=["POST"])
|
||||||
|
def login_request_avatar():
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.json['username']
|
||||||
|
user = Users.query.filter_by(username=username).first()
|
||||||
|
if user.s_avatar != "default":
|
||||||
|
path = f"static/{user.s_avatar}"
|
||||||
|
filename = f"{user.username}.png"
|
||||||
|
return send_file(open(path, "rb"), download_name=filename)
|
||||||
|
return Response(status=201)
|
||||||
|
|
||||||
|
@app.route('/check_server')
|
||||||
|
def ping():
|
||||||
|
return Response(status=200)
|
||||||
|
|
||||||
|
@app.route('/update_app/<pipe>/<version>')
|
||||||
|
def update_app(pipe, version):
|
||||||
|
current_latest = "alpha_v1.0"
|
||||||
|
if pipe == "latest":
|
||||||
|
if not current_latest == version:
|
||||||
|
update_ready = True
|
||||||
|
else:
|
||||||
|
update_ready = False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'latest_version': current_latest,
|
||||||
|
'update_ready': update_ready,
|
||||||
|
'download_url': f"/download_app/{pipe}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return jsonify(data=data)
|
||||||
|
|
||||||
|
return Response(status=200)
|
||||||
|
|
||||||
|
@app.route("/upload_file", methods=["POST"])
|
||||||
|
def save_chunks():
|
||||||
|
if 'file' in request.files:
|
||||||
|
uploaded_file = request.files['file']
|
||||||
|
filename = uploaded_file.filename
|
||||||
|
with open(f'{app.config['UPLOAD_FOLDER']}/{filename}', 'ab') as f:
|
||||||
|
f.write(uploaded_file.read())
|
||||||
|
return '', 200
|
||||||
|
return 404
|
||||||
|
|
||||||
|
@app.route("/upload_info", methods=['POST'])
|
||||||
|
async def upload_info():
|
||||||
|
if not request.method == 'POST':
|
||||||
|
return Response(status=415)
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
|
||||||
|
user = Users.query.filter_by(username=data['username']).first()
|
||||||
|
if not check_password(data['password'], user.password):
|
||||||
|
return Response(status=415)
|
||||||
|
filename = data['filename']
|
||||||
|
|
||||||
|
# database upload
|
||||||
|
date = datetime.date.today()
|
||||||
|
save_path = f"{app.config['UPLOAD_FOLDER']}/{filename}"
|
||||||
|
meta_data = data['meta_data']
|
||||||
|
collection_json = data['collection_json']
|
||||||
|
character_links = data['character_links']
|
||||||
|
mod_list = data['mod_list']
|
||||||
|
print(meta_data)
|
||||||
|
upload = Uploads(
|
||||||
|
userid=user.id,
|
||||||
|
upload_date = date,
|
||||||
|
filename=filename,
|
||||||
|
author=user.username,
|
||||||
|
version="test",
|
||||||
|
description="",
|
||||||
|
meta_data = meta_data,
|
||||||
|
collection_json = collection_json,
|
||||||
|
character_links = character_links,
|
||||||
|
mod_list = mod_list,
|
||||||
|
path=save_path
|
||||||
|
)
|
||||||
|
db.session.add(upload)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return Response(status=200)
|
||||||
|
|
||||||
|
@app.route("/upload_app", methods=['POST'])
|
||||||
|
async def upload_app():
|
||||||
|
data = request.files['datas'].read()
|
||||||
|
data = json.loads(data)
|
||||||
|
if not request.method == 'POST':
|
||||||
|
return Response(status=415)
|
||||||
|
|
||||||
|
user = Users.query.filter_by(username=data['username']).first()
|
||||||
|
if not check_password(data['password'], user.password):
|
||||||
|
return Response(status=415)
|
||||||
|
|
||||||
|
if 'file' not in request.files:
|
||||||
|
print(request.files)
|
||||||
|
return Response(status=415)
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
|
||||||
|
if file.filename == '':
|
||||||
|
return Response(status=415)
|
||||||
|
filename = data['filename']
|
||||||
|
filename = secure_filename(filename)
|
||||||
|
|
||||||
|
# database upload
|
||||||
|
date = datetime.date.today()
|
||||||
|
save_path = f"{app.config['UPLOAD_FOLDER']}/{filename}"
|
||||||
|
meta_data = data['meta_data']
|
||||||
|
collection_json = data['collection_json']
|
||||||
|
character_links = data['character_links']
|
||||||
|
mod_list = data['mods_to_copy']
|
||||||
|
print(meta_data)
|
||||||
|
upload = Uploads(
|
||||||
|
userid=user.id,
|
||||||
|
upload_date = date,
|
||||||
|
filename=filename,
|
||||||
|
author=user.firstname,
|
||||||
|
version="test",
|
||||||
|
description="",
|
||||||
|
meta_data = meta_data,
|
||||||
|
collection_json = collection_json,
|
||||||
|
character_links = character_links,
|
||||||
|
mod_list = mod_list,
|
||||||
|
path=save_path
|
||||||
|
)
|
||||||
|
db.session.add(upload)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# saves file
|
||||||
|
print(save_path)
|
||||||
|
file.save(save_path)
|
||||||
|
return Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login_app", methods=["POST"])
|
||||||
|
def login_app():
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.json['username']
|
||||||
|
user = Users.query.filter_by(username=username).first()
|
||||||
|
data= {
|
||||||
|
"id": user.id,
|
||||||
|
"friendcode": user.friendcode,
|
||||||
|
"subs": user.subs,
|
||||||
|
}
|
||||||
|
if check_password(request.json['password'], user.password):
|
||||||
|
return data, 200
|
||||||
|
return Response(status=500)
|
||||||
|
|
||||||
|
@app.route("/login", methods=["GET", "POST"])
|
||||||
|
def login():
|
||||||
|
if request.method == "POST":
|
||||||
|
user = Users.query.filter_by(username=request.form.get("username")).first()
|
||||||
|
if check_password(request.form.get("password"), user.password):
|
||||||
|
login_user(user)
|
||||||
|
return redirect(url_for("home"))
|
||||||
|
return render_template("login.html")
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for("home"))
|
||||||
|
|
||||||
|
@app.route("/signup", methods=["GET", "POST"])
|
||||||
|
def signup():
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.form.get('password') == request.form.get('confirm_password'):
|
||||||
|
user = Users(
|
||||||
|
username=request.form.get('username'),
|
||||||
|
email=request.form.get("email"),
|
||||||
|
password=password_hash(request.form.get("password")),
|
||||||
|
firstname=request.form.get('firstname'),
|
||||||
|
lastname=request.form.get('lastname'),
|
||||||
|
friendcode=friend_code(6),
|
||||||
|
subs=[],
|
||||||
|
raw_avatar='default',
|
||||||
|
s_avatar='default',
|
||||||
|
m_avatar='default',
|
||||||
|
l_avatar='default'
|
||||||
|
)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
else:
|
||||||
|
return redirect(request.url)
|
||||||
|
return render_template("signup.html")
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def home():
|
||||||
|
return render_template("home.html")
|
||||||
|
|
||||||
|
@app.route("/about")
|
||||||
|
def about():
|
||||||
|
return render_template("about.html")
|
||||||
|
|
||||||
|
was_upload = (False, "")
|
||||||
|
|
||||||
|
@app.route("/collections/upload", methods=["POST"])
|
||||||
|
def upload():
|
||||||
|
if request.method == "POST":
|
||||||
|
if 'file' not in request.files:
|
||||||
|
print('no file')
|
||||||
|
flash('No file part')
|
||||||
|
return redirect(url_for('collections'))
|
||||||
|
file = request.files['file']
|
||||||
|
if file.filename == '':
|
||||||
|
print("no selected file")
|
||||||
|
flash('No selected file')
|
||||||
|
return redirect(url_for('collections'))
|
||||||
|
if file:
|
||||||
|
filename= secure_filename(file.filename)
|
||||||
|
global was_upload
|
||||||
|
was_upload = (True, filename)
|
||||||
|
print(filename)
|
||||||
|
path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
|
file.save(path)
|
||||||
|
|
||||||
|
date = datetime.date.today()
|
||||||
|
|
||||||
|
upload = Uploads(
|
||||||
|
userid=current_user.id,
|
||||||
|
upload_date = date,
|
||||||
|
filename=filename,
|
||||||
|
author=current_user.username,
|
||||||
|
version="test",
|
||||||
|
description="",
|
||||||
|
path=path
|
||||||
|
)
|
||||||
|
db.session.add(upload)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return redirect(url_for('collections'))
|
||||||
|
|
||||||
|
def generate_file_chunks(filename):
|
||||||
|
with open(filename, 'rb') as file:
|
||||||
|
while True:
|
||||||
|
chunk = file.read(4096) # Read 4096 bytes at a time
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/download_app/latest")
|
||||||
|
def download_app():
|
||||||
|
path = pathlib.Path(f"static/versions/latest.zip")
|
||||||
|
filename = "Collection Sharing App.zip"
|
||||||
|
return send_file(open(path, "rb"), download_name=filename)
|
||||||
|
|
||||||
|
@app.route("/collections/download/<id>")
|
||||||
|
def download(id):
|
||||||
|
upload = Uploads.query.filter_by(id=id).first()
|
||||||
|
abs_path = os.path.join(current_app.root_path, str(upload.path).replace('/', '\\'))
|
||||||
|
print(abs_path)
|
||||||
|
if not os.path.isfile(abs_path):
|
||||||
|
print("File not found!")
|
||||||
|
try:
|
||||||
|
with open(abs_path, "rb") as f:
|
||||||
|
print("File read success, first 10 bytes:", f.read(10))
|
||||||
|
except Exception as e:
|
||||||
|
print("Error reading file:", e)
|
||||||
|
filename = upload.filename
|
||||||
|
return send_file(abs_path, as_attachment=True, download_name=filename)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/collections/delete/<id>", methods=["GET", "POST"])
|
||||||
|
def delete_collection(id):
|
||||||
|
upload = Uploads.query.filter_by(id=id).first()
|
||||||
|
os.remove(upload.path)
|
||||||
|
row_to_delete = db.session.query(Uploads).filter(Uploads.id == id).one()
|
||||||
|
db.session.delete(row_to_delete)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('collections'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/collections/update/<id>", methods=["GET", "POST"])
|
||||||
|
def update_collection(id):
|
||||||
|
print(f"this is an update call for id:{id}")
|
||||||
|
if request.method == "POST":
|
||||||
|
print(f"this is an update call for id:{id}, request is a POST")
|
||||||
|
upload = Uploads.query.filter_by(id=id).first()
|
||||||
|
upload.author = request.form.get(f"author-{id}")
|
||||||
|
upload.version = request.form.get(f"version-{id}")
|
||||||
|
upload.description = request.form.get(f"description-{id}")
|
||||||
|
print(upload.description)
|
||||||
|
db.session.commit()
|
||||||
|
if request.form.get(f"latest-{id}") == "on":
|
||||||
|
user = Users.query.filter_by(id=current_user.id).first()
|
||||||
|
user.latest = upload.id
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('collections'))
|
||||||
|
return redirect(url_for('collections'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/collections")
|
||||||
|
def collections():
|
||||||
|
page = request.args.get('page', 1, type=int)
|
||||||
|
pagination = Uploads.query.filter_by(userid=current_user.id).order_by(Uploads.upload_date)
|
||||||
|
pagination = pagination.paginate(page=page, per_page=5, error_out=False)
|
||||||
|
data=[]
|
||||||
|
friends_data = []
|
||||||
|
for code in current_user.subs:
|
||||||
|
try:
|
||||||
|
user = Users.query.filter_by(friendcode=code).first()
|
||||||
|
upload = Uploads.query.filter_by(id=user.latest).first()
|
||||||
|
up_data = {
|
||||||
|
"id": upload.id,
|
||||||
|
"upload_date": upload.upload_date,
|
||||||
|
"name": upload.filename,
|
||||||
|
"author": upload.author,
|
||||||
|
"version": upload.version,
|
||||||
|
"description": upload.description,
|
||||||
|
"path": pathlib.Path(f"{upload.path}").absolute()
|
||||||
|
}
|
||||||
|
friends_data.append(up_data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
global was_upload
|
||||||
|
data=[pagination, friends_data, deepcopy(was_upload)]
|
||||||
|
was_upload = (False, "")
|
||||||
|
return render_template("collections.html", data=data)
|
||||||
|
|
||||||
|
@app.route('/profile')
|
||||||
|
def profile():
|
||||||
|
return render_template("profile.html")
|
||||||
|
|
||||||
|
@app.route('/profile/upload', methods=["POST"])
|
||||||
|
def upload_avatar():
|
||||||
|
if request.method == 'POST':
|
||||||
|
f = request.files.get('file')
|
||||||
|
print(f)
|
||||||
|
raw_filename = avatars.save_avatar(f)
|
||||||
|
user = Users.query.filter_by(id=current_user.id).first()
|
||||||
|
user.raw_avatar = f"{app.config['AVATARS_SAVE_PATH']}{raw_filename}"
|
||||||
|
db.session.commit()
|
||||||
|
session['raw_filename'] = raw_filename # you will need to store this filename in database in reality
|
||||||
|
return redirect(url_for('crop'))
|
||||||
|
return redirect(url_for('profile'))
|
||||||
|
|
||||||
|
# serve avatar image
|
||||||
|
@app.route('/avatars/<path:filename>')
|
||||||
|
def get_avatar(filename):
|
||||||
|
return send_from_directory(app.config['AVATARS_SAVE_PATH'], filename)
|
||||||
|
|
||||||
|
@app.route('/crop', methods=['GET', 'POST'])
|
||||||
|
def crop():
|
||||||
|
if request.method == 'POST':
|
||||||
|
x = request.form.get('x')
|
||||||
|
y = request.form.get('y')
|
||||||
|
w = request.form.get('w')
|
||||||
|
h = request.form.get('h')
|
||||||
|
filenames = avatars.crop_avatar(session['raw_filename'], x, y, w, h)
|
||||||
|
user = Users.query.filter_by(id=current_user.id).first()
|
||||||
|
user.s_avatar = f"avatars/{filenames[0]}"
|
||||||
|
print(user.s_avatar)
|
||||||
|
db.session.commit()
|
||||||
|
user.m_avatar = f"avatars/{filenames[1]}"
|
||||||
|
db.session.commit()
|
||||||
|
user.l_avatar = f"avatars/{filenames[2]}"
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return redirect(url_for('profile'))
|
||||||
|
return render_template('crop.html')
|
||||||
|
|
||||||
|
@app.route('/profile/friends')
|
||||||
|
def friends_code():
|
||||||
|
data = []
|
||||||
|
for code in current_user.subs:
|
||||||
|
try:
|
||||||
|
user = Users.query.filter_by(friendcode=code).first()
|
||||||
|
|
||||||
|
x = {
|
||||||
|
'username': user.username,
|
||||||
|
'friendcode': user.friendcode,
|
||||||
|
'm_avatar': user.m_avatar
|
||||||
|
}
|
||||||
|
|
||||||
|
data.append(x)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return render_template("friends.html", data=data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/addcode", methods=["POST"])
|
||||||
|
def addcode():
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.form.get("friendcode") == "":
|
||||||
|
return redirect(url_for('friends_code'))
|
||||||
|
user = Users.query.filter_by(id=current_user.id).first()
|
||||||
|
user.subs.append(request.form.get("friendcode"))
|
||||||
|
user.subs = user.subs
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('friends_code'))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port="5003", debug=True)
|
||||||
0
mysite/mysite/__init__.py
Normal file
0
mysite/mysite/__init__.py
Normal file
BIN
mysite/mysite/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
mysite/mysite/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
mysite/mysite/__pycache__/settings.cpython-312.pyc
Normal file
BIN
mysite/mysite/__pycache__/settings.cpython-312.pyc
Normal file
Binary file not shown.
BIN
mysite/mysite/__pycache__/urls.cpython-312.pyc
Normal file
BIN
mysite/mysite/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
mysite/mysite/__pycache__/wsgi.cpython-312.pyc
Normal file
BIN
mysite/mysite/__pycache__/wsgi.cpython-312.pyc
Normal file
Binary file not shown.
16
mysite/mysite/asgi.py
Normal file
16
mysite/mysite/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for mysite project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
124
mysite/mysite/settings.py
Normal file
124
mysite/mysite/settings.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
"""
|
||||||
|
Django settings for mysite project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.0.4.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-zkb)))rp%reh1qr6ega!sn41^b(-93*)vb&u2k(h6ltk!wion9'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'catalog.apps.CatalogConfig'
|
||||||
|
]
|
||||||
|
|
||||||
|
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',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'mysite.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 = 'mysite.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.0/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/5.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
23
mysite/mysite/urls.py
Normal file
23
mysite/mysite/urls.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for mysite project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('catalog/', include('catalog.urls')),
|
||||||
|
]
|
||||||
16
mysite/mysite/wsgi.py
Normal file
16
mysite/mysite/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for mysite 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/5.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
BIN
saved_file.zip
Normal file
BIN
saved_file.zip
Normal file
Binary file not shown.
0
static/css/template.css
Normal file
0
static/css/template.css
Normal file
24
templates/about.html
Normal file
24
templates/about.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>About Flask</title>
|
||||||
|
<!-- Compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
|
||||||
|
|
||||||
|
<!-- Compiled and minified JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1> About Flask </h1>
|
||||||
|
<p> Flask is a micro web framework written in Python.</p>
|
||||||
|
<p> Applications that use the Flask framework include Pinterest,
|
||||||
|
LinkedIn, and the community web page for Flask itself.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
111
templates/collection_list.html
Normal file
111
templates/collection_list.html
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<h3> My Collections </h3>
|
||||||
|
<table class="highlight">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Collection Name</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for item in data[0].items %}
|
||||||
|
<tr>
|
||||||
|
<div class="modal-trigger" href="#demo-modal-{{item.id}}">
|
||||||
|
{% if item.id == current_user.latest %}
|
||||||
|
<td><i class="tiny material-icons">star</i>{{item.filename}}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{item.filename}}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td>{{item.author}}</td>
|
||||||
|
<td>{{item.version}}</td>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<td class="right-align">
|
||||||
|
<a class="waves-effect waves-light green btn-small right-align modal-trigger" data-target="modal-{{item.id}}"><i class="material-icons center">edit</i></a>
|
||||||
|
<a class="waves-effect waves-light grey btn-small right-align" href="{{ url_for('download', id=item.id)}}" download><i class="material-icons center">download</i></a>
|
||||||
|
<a class="waves-effect waves-light red btn-small right-align" href="{{ url_for('delete_collection', id=item.id)}}"><i class="material-icons center">delete</i></a>
|
||||||
|
</td>
|
||||||
|
<div id="modal-{{item.id}}" class="modal" style="width: 60%">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>{{item.filename}}</h4>
|
||||||
|
|
||||||
|
<form action="{{ url_for('update_collection', id=item.id) }}", method="post">
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12 m6">
|
||||||
|
<input value="{{item.author}}" placeholder="Foo" id="author-{{item.id}}" name="author-{{item.id}}" type="text" class="validate">
|
||||||
|
<label for="author">Author</label>
|
||||||
|
</div>
|
||||||
|
<label class="right col s2">
|
||||||
|
{% if item.id == current_user.latest %}
|
||||||
|
<input type="checkbox" class="filled-in right" checked="checked" id="latest-{{item.id}}" name="latest-{{item.id}}" />
|
||||||
|
<span>Latest</span>
|
||||||
|
{% else %}
|
||||||
|
<input type="checkbox" id="latest-{{item.id}}" name="latest-{{item.id}}" />
|
||||||
|
<span>Latest</span>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12 m6">
|
||||||
|
<input value="{{item.version}}" placeholder="v2024.1.1" id="version-{{item.id}}" name="version-{{item.id}}" type="text" class="validate">
|
||||||
|
<label for="version">Version</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12 m6">
|
||||||
|
<input value="{{item.upload_date}}" placeholder="1/1/2001" id="upload_date-{{item.id}}" name="upload_date-{{item.id}}" type="text" class="validate">
|
||||||
|
<label for="upload_date">Uploaded</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<textarea id="description-{{item.id}}" name="description-{{item.id}}" class="materialize-textarea">{{item.description}}</textarea>
|
||||||
|
<label for="description">Description</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s2">
|
||||||
|
<a class="waves-effect waves-light grey btn left-align" href="{{ url_for('download', id=item.id)}}" download>Download<i class="material-icons right">cloud_download</i></a>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s2 right">
|
||||||
|
<button class="btn waves-effect waves-light right" type="submit" name="action" id="submit">Save
|
||||||
|
<i class="material-icons right">save</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ul class="pagination center">
|
||||||
|
{% if data[0].has_prev %}
|
||||||
|
<li class='waves-effect'><a class='waves-effect' href="{{ url_for('collections', page=data[0].prev_num) }}"><i class="material-icons">chevron_left</i></a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for number in data[0].iter_pages() %}
|
||||||
|
{% if data[0].page != number %}
|
||||||
|
<li class='waves-effect'><a href="{{ url_for('collections', page=number) }}">{{ number }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class='active'><a>{{ number }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if data[0].has_next %}
|
||||||
|
<li class='waves-effect'><a href="{{ url_for('collections', page=data[0].next_num) }}"><i class="material-icons">chevron_right</i></a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
86
templates/collections.html
Normal file
86
templates/collections.html
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Treehouse - My Collections</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container" style="width: 85%; background-color: white;">
|
||||||
|
<div class="section">
|
||||||
|
<form method="post" action="{{ url_for('upload')}}" enctype="multipart/form-data">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s10 m10 l10">
|
||||||
|
<div class="file-field input-field center">
|
||||||
|
<div class="btn grey">
|
||||||
|
<span>File</span>
|
||||||
|
<input name="file" type="file">
|
||||||
|
</div>
|
||||||
|
<div class="file-path-wrapper">
|
||||||
|
<input class="file-path validate" type="text" placeholder="Upload a file...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s2 m2 l2">
|
||||||
|
<div class="input-field">
|
||||||
|
<button type="submit" class="btn grey" name=action>
|
||||||
|
Upload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
{% include "collection_list.html" %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<h3> Friends Collections </h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Collection Name</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in data[1] %}
|
||||||
|
<tr>
|
||||||
|
<td>{{item.name}}</td>
|
||||||
|
<td>{{item.author}}</td>
|
||||||
|
<td>{{item.version}}</td>
|
||||||
|
<td><a class="waves-effect waves-light btn-small right" href="{{ url_for('download', id=item.id)}}" download><i class="material-icons left">download</i>Download</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if data[2][0] %}
|
||||||
|
<script> M.toast({html: "Uploaded {{data[2][1]}}!"})</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.modal').modal();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
25
templates/crop.html
Normal file
25
templates/crop.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Flask-Avatars Demo</title>
|
||||||
|
{{ avatars.jcrop_css() }} <!-- include jcrop css -->
|
||||||
|
<style>
|
||||||
|
<!-- some css to make a better preview window -->
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Step 2: Crop</h1>
|
||||||
|
{{ avatars.crop_box('get_avatar', session['raw_filename']) }} <!-- crop window -->
|
||||||
|
{{ avatars.preview_box('get_avatar', session['raw_filename']) }} <!-- preview widow -->
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" id="x" name="x">
|
||||||
|
<input type="hidden" id="y" name="y">
|
||||||
|
<input type="hidden" id="w" name="w">
|
||||||
|
<input type="hidden" id="h" name="h">
|
||||||
|
<input type="submit" value="Crop!">
|
||||||
|
</form>
|
||||||
|
{{ avatars.jcrop_js() }} <!-- include jcrop javascript -->
|
||||||
|
{{ avatars.init_jcrop() }} <!-- init jcrop -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
45
templates/friends.html
Normal file
45
templates/friends.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="card-panel grey lighten-5 z-depth-1">
|
||||||
|
<div class="col s12 center">
|
||||||
|
<h1>Friends</h1>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="row">
|
||||||
|
<form action="{{url_for('addcode')}}" method="post">
|
||||||
|
<div class="offset-s4 input-field col s4">
|
||||||
|
<input placeholder="Placeholder" id="friendcode" name="friendcode" type="text" class="validate">
|
||||||
|
<label for="friendcode">Enter your FriendCode</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s2">
|
||||||
|
<button type="submit" class="btn waves-effect waves-light green" name=action>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<ul class="collection">
|
||||||
|
{% for friend in data %}
|
||||||
|
<li class="collection-item avatar">
|
||||||
|
|
||||||
|
{% if friend.m_avatar == 'default' %}
|
||||||
|
<img src="{{ avatars.default() }}" alt="" class="circle">
|
||||||
|
{% else %}
|
||||||
|
<img src="{{url_for("static", filename=friend.m_avatar)}}" class="circle responsive-img">
|
||||||
|
{% endif %}
|
||||||
|
<h5>{{friend.username}}</h5>
|
||||||
|
<h5 class="secondary-content">{{friend.friendcode}}</h5>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
34
templates/home.html
Normal file
34
templates/home.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Treehouse - Home</title>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<!-- <img style="object-fit: fill;" src="{{url_for('static', filename='images/2024-03-03 225803255514-gen-image-HJpDVk.jpg')}}"> -->
|
||||||
|
<div class="container section">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 m12 l6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Collection Sharing App alpha_V1.0</span>
|
||||||
|
<p>Here is the latest version of the Collection Sharing App as of 11-23-2024. You can login to an account you set up under "sign-up" on this webpage
|
||||||
|
and upload directly to this site OR you can just export and import specific collections and share them through another means. That being said this is very much
|
||||||
|
an ALPHA app and will have its issues.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<a class="waves-effect waves-light grey btn left-align" href="{{ url_for('download_app')}}" download>Latest<i class="material-icons right">cloud_download</i></a>
|
||||||
|
<p class="right">alpha_v1.0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
28
templates/login.html
Normal file
28
templates/login.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Signup</title>
|
||||||
|
|
||||||
|
<!-- Compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
|
||||||
|
|
||||||
|
<!-- Compiled and minified JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}
|
||||||
|
<div class="container section">
|
||||||
|
<h1>Login to your account</h1>
|
||||||
|
<form action="#" method="post">
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" name="username" />
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" name="password" />
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
75
templates/profile.html
Normal file
75
templates/profile.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container" style="background-color: white;">
|
||||||
|
<div class="col s12 m8">
|
||||||
|
<div class="card-panel grey lighten-5 z-depth-1">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s4">
|
||||||
|
{% if current_user.l_avatar == 'default' %}
|
||||||
|
<img src="{{ avatars.default() }}" alt="" class="circle">
|
||||||
|
{% else %}
|
||||||
|
<img src="{{url_for("static", filename=current_user.l_avatar)}}" class="circle responsive-img">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col s4 center">
|
||||||
|
<h1>Profile</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12 valign-center">
|
||||||
|
<input value="{{current_user.username}}" placeholder="Placeholder" id="username" type="text" class="validate" disabled>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input value="{{current_user.email}}" placeholder="" id="email" type="text" class="validate" disabled>
|
||||||
|
<label for="email">Email</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input value="{{current_user.friendcode}}" placeholder="" id="friendcode" type="text" class="validate " disabled>
|
||||||
|
<label for="friendcode">Friend Code</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input value="{{current_user.firstname}}" placeholder="" id="firstname" type="text" class="validate ">
|
||||||
|
<label for="firstname">First Name</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input value="{{current_user.lastname}}" placeholder="" id="lastname" type="text" class="validate ">
|
||||||
|
<label for="lastname">Last Name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" action="/profile/upload" id="upload_avatar" enctype="multipart/form-data">
|
||||||
|
<div class="row col s12">
|
||||||
|
<div class="col s9">
|
||||||
|
<div class="file-field input-field">
|
||||||
|
<div class="btn green" type="file">
|
||||||
|
<span>File</span>
|
||||||
|
<input name="file" type="file">
|
||||||
|
</div>
|
||||||
|
<div class="file-path-wrapper">
|
||||||
|
<input class="file-path validate" type="text" placeholder="Change/Upload Avatar...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s3">
|
||||||
|
<div class="input-field">
|
||||||
|
<button class="btn waves-effect waves-light green" type="submit" name=action form="upload_avatar">Submit
|
||||||
|
<i class="material-icons right">send</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
112
templates/signup.html
Normal file
112
templates/signup.html
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Signup</title>
|
||||||
|
|
||||||
|
<!-- Compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
|
||||||
|
|
||||||
|
<!-- Compiled and minified JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}
|
||||||
|
<div class="container section">
|
||||||
|
<h1 class="center">Signup</h1>
|
||||||
|
<div class="row center">
|
||||||
|
<form class="col s12" method="post">
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input placeholder="Placeholder" id="first_name" type="text" class="validate" name="firstname">
|
||||||
|
<label for="first_name">First Name</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input id="last_name" type="text" class="validate" name="lastname">
|
||||||
|
<label for="last_name">Last Name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input id="username" type="text" class="validate" name="username">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s5">
|
||||||
|
<input id="password" type="password" class="validate" name="password">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s5">
|
||||||
|
<input id="confirm_password" type="password" class="validate" name="confirm_password">
|
||||||
|
<label for="confirm_password">Confirm Password</label>
|
||||||
|
</div>
|
||||||
|
<div class="col s2 left-align">
|
||||||
|
<p id="message"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input id="email" type="email" class="validate" name="email">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<button class="btn waves-effect waves-light" type="submit" name="action" id="submit">Submit
|
||||||
|
<i class="material-icons right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let match = false
|
||||||
|
let input = document.getElementById("username")
|
||||||
|
let button = document.getElementById("submit")
|
||||||
|
let pass = document.getElementById('password')
|
||||||
|
let con_pass = document.getElementById('confirm_password')
|
||||||
|
let message = document.getElementById('message')
|
||||||
|
|
||||||
|
button.disabled = true
|
||||||
|
|
||||||
|
input.addEventListener("change", stateHandle)
|
||||||
|
pass.addEventListener("change", checkPassword)
|
||||||
|
con_pass.addEventListener("change", checkPassword)
|
||||||
|
pass.addEventListener("change", stateHandle)
|
||||||
|
con_pass.addEventListener("change", stateHandle)\
|
||||||
|
|
||||||
|
function checkPassword() {
|
||||||
|
if (pass.value == "" || con_pass.value == "") {
|
||||||
|
console.log('empty')
|
||||||
|
message.style.color = 'red';
|
||||||
|
message.innerHTML = '';
|
||||||
|
match = false
|
||||||
|
} else if (pass.value != con_pass.value) {
|
||||||
|
console.log('not')
|
||||||
|
message.style.color = 'red';
|
||||||
|
message.innerHTML = 'not matching';
|
||||||
|
match = false
|
||||||
|
} else if (pass.value ==
|
||||||
|
con_pass.value) {
|
||||||
|
console.log('equal')
|
||||||
|
|
||||||
|
message.style.color = 'green';
|
||||||
|
message.innerHTML = 'matching';
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stateHandle() {
|
||||||
|
console.log(match)
|
||||||
|
if (document.getElementById("username").value != "" && match == true) {
|
||||||
|
button.disabled = false
|
||||||
|
} else {
|
||||||
|
button.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
templates/template.html
Normal file
69
templates/template.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Treehouse - Navbar</title>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<ul id='dropdown' class='dropdown-content'>
|
||||||
|
<li><a href="{{ url_for('profile')}}">Profile</a></li>
|
||||||
|
<li><a href="{{ url_for('friends_code')}}">Friend Code</a></li>
|
||||||
|
<li><a href="{{ url_for('logout') }}">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<nav class="black-text z-depth-0" style="font-family: Arial, Helvetica, sans-serif;">
|
||||||
|
<div class="nav-wrapper green lighten-2 black-text">
|
||||||
|
<a href="#" data-target="mobile-demo" class="sidenav-trigger"><i class="material-icons">menu</i></a>
|
||||||
|
<ul id="nav-mobile" class="hide-on-med-and-down">
|
||||||
|
<li><a href="{{ url_for('home') }}">Home</a></li>
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li><a href="{{ url_for('collections') }}">Collections</a></li>
|
||||||
|
<li style="" class="right"><a style="font-size: 16px; font-family: Arial, Helvetica, sans-serif;" class="dropdown-trigger" href="#!" data-target="dropdown">
|
||||||
|
<img style="vertical-align: top; padding-right: 10px; width: 60px; padding-top: 5px" class="image circle center-valign" src="{{url_for("static", filename=current_user.m_avatar)}}">{{current_user.username}}
|
||||||
|
<i class="material-icons right">arrow_drop_down</i></a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="right"><a href="{{ url_for('signup') }}">Sign Up</a></li>
|
||||||
|
<li class="right"><a href="{{ url_for('login') }}">Login</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<ul class="sidenav" id="mobile-demo">
|
||||||
|
<li><a href="{{ url_for('home') }}">Home</a></li>
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li><a href="{{ url_for('collections') }}">Collections</a></li>
|
||||||
|
<li><a class="" href="#!" data-target="">{{current_user.username}}<i class="material-icons right">arrow_drop_down</i></a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{ url_for('signup') }}">Sign Up</a></li>
|
||||||
|
<li><a href="{{ url_for('login') }}">Login</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
<script>
|
||||||
|
$(".dropdown-trigger").dropdown({hover: false});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('.sidenav').sidenav();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.modal').modal();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
test.py
Normal file
36
test.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import requests
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
def basic_auth(username, password):
|
||||||
|
token = b64encode(f"{username}:{password}".encode('utf-8')).decode("ascii")
|
||||||
|
return f'Basic {token}'
|
||||||
|
|
||||||
|
|
||||||
|
requests.post("https://ntfy.treehousefullofstars.com/alerts",
|
||||||
|
data="Look ma, with auth",
|
||||||
|
headers={
|
||||||
|
"Authorization": basic_auth('jadowyne', 'Jumbocarrot&001')
|
||||||
|
})
|
||||||
|
|
||||||
|
already_used = [
|
||||||
|
[(False, None), (False, None), (True, "X")],
|
||||||
|
[(False, None), (True, "Circle"), (False, None)],
|
||||||
|
[(False, None), (False, None), (False, None)]
|
||||||
|
]
|
||||||
|
|
||||||
|
already_used = [(0, 0),(0, 1),(0, 2), ...]
|
||||||
|
|
||||||
|
|
||||||
|
spot = random(len(already_used.keys()))
|
||||||
|
|
||||||
|
amount_x = 3
|
||||||
|
amount_y = 3
|
||||||
|
|
||||||
|
|
||||||
|
grid = [
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
2, 2
|
||||||
|
state = already_used[x][y][0]
|
||||||
Loading…
x
Reference in New Issue
Block a user