Merge pull request #47 from th3r00t/newui

Pull user system into development
This commit is contained in:
th3r00t
2020-07-18 20:28:24 -04:00
committed by GitHub
13 changed files with 257 additions and 73 deletions

View File

@@ -114,7 +114,6 @@ class Storage:
return True
def make_collections(self):
breakpoint()
_title_regx = re.compile(r"^[0-9][0-9]*|-|\ \B")
_q = "SELECT id,file_name FROM books"
self.cursor.execute(_q)
@@ -123,8 +122,11 @@ class Storage:
path = self.config.book_path + "/"
_collections = []
_pathing = book[1].split(path)[1].split("/")
_pathing.pop(0)
_pathing.pop(-1)
try:
_pathing.pop(0)
_pathing.pop(-1)
except IndexError:
continue
for _p in _pathing:
_s = _p.replace("'", "")
_x = re.sub(_title_regx, "", _s)

View File

@@ -52,7 +52,7 @@ INSTALLED_APPS = [
"interface.templatetags",
"debug_toolbar",
]
AUTH_USER_MODEL = "interface.CustomUser"
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
@@ -135,6 +135,6 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
LOGIN_REDIRECT_URL = 'home'
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "interface/static/")

View File

@@ -16,12 +16,13 @@ Including another URLconf
from django.conf import settings
from django.contrib import admin
from django.urls import include, path, re_path
from django.contrib.auth import views as auth_views
from interface import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.index, name="index"),
path("home", views.home, name="index"),
path("home", views.home, name="home"),
path("sort/<_order>", views.index, name="index"),
path("flip_sort/<_order>", views.flip_sort, name="index"),
path("download/<pk>", views.download, name="download"),
@@ -35,7 +36,30 @@ urlpatterns = [
path("search/", views.index, name="search"),
path("search/<query>", views.index, name="search"),
path("search/<query>/<_set>", views.index, name="search"),
path("show_collection/<_collection>/<_colset>", views.show_collection, name="show_collection",),
path("show_collection/<_collection>/<_colset>", views.show_collection, name="show_collection"),
path("signup", views.signup, name="signup"),
path("login", views.userlogin, name="login"),
path('logout', views.userlogout, name='logout'),
path(
'admin/password_reset/',
auth_views.PasswordResetView.as_view(),
name='admin_password_reset',
),
path(
'admin/password_reset/done/',
auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done',
),
path(
'reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm',
),
path(
'reset/done/',
auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete',
),
]
if settings.DEBUG:
import debug_toolbar

View File

@@ -1,6 +1,30 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import Books, Collections, Favorites, Navigation, CustomUser
from .forms import CustomUserCreationForm, CustomUserChangeForm
class CustomUserAdmin(UserAdmin):
model = CustomUser
add_form = CustomUserCreationForm
form = CustomUserChangeForm
list_display = ["email", "username", "facebook", "twitter", "sponsorid", "matrixid"]
fieldsets = UserAdmin.fieldsets + (
(None, {"fields": ()}),
("Personal info", {"fields": ("facebook", "twitter", "matrixid")}),
("Permissions", {"fields": ("sponsorid",)}),
)
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = UserAdmin.add_fieldsets + (
(None, {"classes": ("wide",), "fields": ("facebook", "twitter", "sponsorid", "matrixid")},),
)
from .models import Books
# Register your models here.
admin.site.register(Books)
admin.site.register(Collections)
admin.site.register(Favorites)
admin.site.register(Navigation)
admin.site.register(CustomUser, CustomUserAdmin)

37
src/interface/forms.py Normal file
View File

@@ -0,0 +1,37 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ("username", "email", "facebook", "twitter", "sponsorid", "matrixid")
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ("username", "email", "facebook", "twitter", "sponsorid", "matrixid")
class CustomUserLoginForm(AuthenticationForm):
class Meta:
Model = CustomUser
fields = ("username", "password")
class SignUpForm(CustomUserCreationForm):
username = forms.CharField(max_length=30, required=False, help_text='Required.')
email = forms.EmailField(max_length=254, help_text='Not yet required, as I\'m still deciding how to handle mail')
matrixid = forms.CharField(max_length=30, required=False, help_text='Optional.')
class Meta:
model = CustomUser
fields = ("username", "email", "matrixid")
class UserLoginForm(CustomUserLoginForm):
class Meta:
model = CustomUser

View File

@@ -1,5 +1,7 @@
from django.contrib.postgres.search import SearchVector
from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractUser
# Create your models here.
@@ -40,12 +42,13 @@ class Books(models.Model):
def generic_search(self, query):
try:
results = Books.objects.annotate(
search=SearchVector("title", "file_name", "author","tags"),
search=SearchVector("title", "file_name", "author", "tags"),
).filter(search=query)
except Exception as e:
raise
return results
class Collections(models.Model):
class Meta:
db_table = "collections"
@@ -69,6 +72,7 @@ class Collections(models.Model):
raise
return results
class Navigation(models.Model):
"""
pyShelfs Navigation Database class
@@ -105,38 +109,6 @@ class Navigation(models.Model):
raise
return results
class Users(models.Model):
"""
pyShelfs User Database class
:param uname: User Name
:param fname: First Name
:param lname: Last Name
:param email: User Email Address
:param password: User Password
:param ulvl: User Level
"""
class Meta:
db_table = "users"
def __str__(self):
return self.title
uname = models.CharField(max_length=255)
fname = models.CharField(max_length=255, null=True)
lname = models.CharField(max_length=255, null=True)
email = models.CharField(max_length=255, null=True, editable=True)
password = models.CharField(max_length=255, null=True)
ulvl = models.IntegerField(null=True)
def generic_search(self, query):
try:
results = Users.objects.annotate(
search=SearchVector("uname", "email", "lname"),
).filter(search=query)
except Exception as e:
raise
return results
class Favorites(models.Model):
"""
@@ -152,7 +124,7 @@ class Favorites(models.Model):
return self.title
favorite = models.ManyToManyField(Books)
uname = models.ManyToManyField(Users)
uname = models.ManyToManyField(settings.AUTH_USER_MODEL)
def generic_search(self, query):
try:
@@ -162,3 +134,11 @@ class Favorites(models.Model):
except Exception as e:
raise
return results
class CustomUser(AbstractUser):
facebook = models.CharField(max_length=255, null=True)
twitter = models.CharField(max_length=255, null=True)
ulvl = models.IntegerField(default=1)
sponsorid = models.IntegerField(null=True)
matrixid = models.CharField(max_length=255, null=True)

View File

@@ -1,3 +1,3 @@
/*! Select2 4.0.7 | https://github.com/select2/select2/blob/master/LICENSE.md */
dfqqyn*! Select2 4.0.7 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"},removeAllItems:function(){return"Καταργήστε όλα τα στοιχεία"}}}),{define:e.define,require:e.require}})();
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"},removeAllItems:function(){return"Καταργήστε όλα τα στοιχεία"}}}),{define:e.define,require:e.require}})();

BIN
src/interface/static/img/inspectocat.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -26,12 +26,6 @@
<input type="hidden" id="_search" name="_search" value="{{ PostedSearch }}" />
<input type="hidden" id="_order" name="_order" value="{{ Order }}" />
<div id="pop_over_0">
<!-- Login Form -->
<div class="hdr_nav" id="hdr_nav_login">
<input id="username" class="nav_login input_box noborder" type="text" size="8" value="Username">
<input id="password" class="nav_login input_box noborder" type="text" size="8" value="Password">
</div>
<!-- End Login Form -->
</div>
<div id="app">
<ul id="shelf_nav">
@@ -43,10 +37,16 @@
<li class="nav_menu_tab"><i class="fas fa-layer-group"></i>&nbsp;Collections</li>
<li class="nav_menu_tab"><i class="fas fa-star"></i>&nbsp;Favorites</li>
<li class="nav_menu_tab"><i class="fas fa-bug"></i>&nbsp;Bug report</li>
<li class="nav_menu_tab" id="btn_login"> <i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;Login</li>
</div>
</div>
<div id="horiz_nav_main">
<li class="nav_menu_tab" id="btn_login"> <i class="fa fa-user-circle" aria-hidden="true"></i>
{% if request.user.is_authenticated %}
<a href='logout' class='nav_link'>&nbsp;{{ request.user }}</a>
{% else %}
<a href="login" class='nav_link'>&nbsp;Login</a>
{% endif %}
</li>
</div>
</div>
<div id="horiz_nav_main">
<div id="horiz_nav_left">
<i class="fas fa-arrow-circle-left nav_icon prev_page" onclick="window.location.href = '/prev_page/{{ Set }}/{{ Order }}'"></i>
&nbsp;sort <i class="fas fa-sort nav_icon"></i>
@@ -88,7 +88,7 @@
{% elif book.description == None %}
<li class="book_description">
<div class="inline_sys_message">
We were unable to find a description in this books metadata.
We were unable to find a description.
<br />Have some <i class="fas fa-drumstick-bite"></i> instead?
</div>
</li>

23
src/interface/templates/login.html vendored Normal file
View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
<button type="submit">Login</button>
</form>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h2>Login</h2>
<form method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
<button type="submit">Login</button>
</form>
</body>
</html>

26
src/interface/templates/signup.html vendored Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>Sign up</h2>
<form method="post">
{% csrf_token %}
{% for field in form %}
<p>
{{ field.label_tag }}<br>
{{ field }}
{% if field.help_text %}
<small style="color: grey">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
</p>
{% endfor %}
<button type="submit">Sign up</button>
</form>
</body>
</html>

View File

@@ -1,4 +1,3 @@
import json
import os
from base64 import b64decode, b64encode
from pathlib import Path
@@ -6,11 +5,13 @@ from pathlib import Path
from backend.lib.config import Config
from django.db import models
from django.http import JsonResponse
from django.shortcuts import HttpResponse, render # render_to_response
from django.shortcuts import HttpResponse, render, redirect # render_to_response
from django.utils.text import slugify
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.models import User
import json
from .models import Books, Collections, Navigation
from .forms import SignUpForm, UserLoginForm
from .models import Books, Collections, Navigation, Favorites
config = Config(Path("../"))
@@ -28,6 +29,40 @@ def index(request, query=None, _set=1, _limit=None, _order='title'):
_payload
)
def signup(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
login(request, user)
return redirect('home')
else:
form = SignUpForm()
return render(request, 'signup.html', {'form': form})
def userlogin(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('home')
form = UserLoginForm()
return render(request, 'login.html', {'form': form})
def userlogout(request):
logout(request)
return redirect('home')
def home(request, query=None, _set=1, _limit=None, _order='title'):
"""
Reset Search Queries & Return Home
@@ -38,6 +73,8 @@ def home(request, query=None, _set=1, _limit=None, _order='title'):
"index.html",
_payload
)
def show_collection(request, _collection, _colset):
try:
_set = int(_colset) + 1
@@ -60,6 +97,7 @@ def show_collection(request, _collection, _colset):
},
)
def flip_sort(request, bookset=1, query=None, _limit=None, _order='title'):
"""
Goto next page in bookset
@@ -73,6 +111,7 @@ def flip_sort(request, bookset=1, query=None, _limit=None, _order='title'):
_payload,
)
def next_page(request, bookset, query=None, _limit=None, _order='title'):
"""
Goto next page in bookset
@@ -88,6 +127,7 @@ def next_page(request, bookset, query=None, _limit=None, _order='title'):
_payload,
)
def prev_page(request, bookset, query=None, _limit=None, _order='title'):
"""
Goto previous page in bookset
@@ -107,6 +147,7 @@ def prev_page(request, bookset, query=None, _limit=None, _order='title'):
_payload,
)
def book_set(_order, _limit=None, _set=1, _flip=False):
"""
Get books results by set #
@@ -121,6 +162,7 @@ def book_set(_order, _limit=None, _set=1, _flip=False):
books = Books.objects.all().order_by(_order)[_set_min:_set_max]
return books
def collection(_collection, _set, _limit=None):
"""
Get books by collection id
@@ -136,6 +178,7 @@ def collection(_collection, _set, _limit=None):
_books.append(c.book_id_id)
return Books.objects.filter(id__in=_books)
def book_set_as_dict(_limit=None, _set=1):
if _limit is None:
_limit = 20
@@ -155,6 +198,7 @@ def book_set_as_dict(_limit=None, _set=1):
}
return json.dumps(_set)
def download(request, pk):
"""
Download book by primary key
@@ -167,17 +211,14 @@ def download(request, pk):
response["Content-Disposition"] = "attachment; filename=%s" % _fn
return response
def favorite(request, pk):
"""
Favorite book by primary key
Add book to favorites bu primary key
"""
_book = Books.objects.all().filter(pk=pk)[0]
_fn = hr_name(_book)
response = HttpResponse(
open(os.path.abspath(_book.file_name), "rb"), content_type="application/zip"
)
response["Content-Disposition"] = "attachment; filename=%s" % _fn
return response
print(Favorite(book=_book, uname=User))
def share(request, pk):
"""
@@ -191,6 +232,7 @@ def share(request, pk):
response["Content-Disposition"] = "attachment; filename=%s" % _fn
return response
def info(request, pk):
"""
Share book by primary key
@@ -203,12 +245,14 @@ def info(request, pk):
response["Content-Disposition"] = "attachment; filename=%s" % _fn
return response
def hr_name(book):
"""
Nicer file names
"""
return "{0}{1}".format(slugify(book.title), os.path.splitext(book.file_name)[1])
def format_list(list_in):
formated_list, formated_list_key, x = [], [], 0
for i in list_in:
@@ -222,6 +266,7 @@ def format_list(list_in):
else:
x = 0
def menu(which, _set=1, parent=None):
if which == "collections":
collection_list = collections
@@ -253,6 +298,7 @@ def menu(which, _set=1, parent=None):
navigation_list = Navigation.objects.all()
return navigation_list
def collections_list():
collection_key = []
for i in collections:
@@ -260,6 +306,7 @@ def collections_list():
collection_key.append(i.collection)
return json.dumps(list(set(collection_key)))
def payload(request, query, _set, _limit, _order, **kwargs):
"""
Return formatted data to template
@@ -267,7 +314,7 @@ def payload(request, query, _set, _limit, _order, **kwargs):
still beautiful
"""
try: request.session['ascending']
except KeyError: request.session['ascending'] = bool
except KeyError: request.session['ascending'] = True
try:
if kwargs['flip_sort']:
request.session['ascending'] = not request.session['ascending']
@@ -309,7 +356,7 @@ def payload(request, query, _set, _limit, _order, **kwargs):
_results.count()
else:
try:
query = request.session['cached_query']
query = request.session['cached_query'] # Is there a cached query?
if query == None: raise KeyError
if request.session['ascending']:
_results = Books().generic_search(query)
@@ -324,11 +371,10 @@ def payload(request, query, _set, _limit, _order, **kwargs):
_r_len, _search = None, None
_bookstats, _collectionstats, _collectionobject = \
Books.objects.all().count, Collections.objects.all().count, \
Books.objects.all().count(), Collections.objects.all().count(), \
collections_list()
if (_r_len): _btotal = _r_len
else: _btotal = _bookstats
if (_r_len): _btotal = str(_r_len)
else: _btotal = str(_bookstats)
return {
"Books": _r,
@@ -342,6 +388,6 @@ def payload(request, query, _set, _limit, _order, **kwargs):
"NowShowing": _now_showing,
"PostedSearch": query,
"SearchLen": _r_len,
"Order": _order
"Order": _order,
}