diff --git a/src/backend/lib/storage.py b/src/backend/lib/storage.py index 0969707..f82c1ec 100755 --- a/src/backend/lib/storage.py +++ b/src/backend/lib/storage.py @@ -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) diff --git a/src/frontend/settings.py b/src/frontend/settings.py index b1ca483..ce8927f 100755 --- a/src/frontend/settings.py +++ b/src/frontend/settings.py @@ -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/") diff --git a/src/frontend/urls.py b/src/frontend/urls.py index 379ece7..6369c3b 100755 --- a/src/frontend/urls.py +++ b/src/frontend/urls.py @@ -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/", views.download, name="download"), @@ -35,7 +36,30 @@ urlpatterns = [ path("search/", views.index, name="search"), path("search/", views.index, name="search"), path("search//<_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///', + 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 diff --git a/src/interface/admin.py b/src/interface/admin.py index 409487d..9a9db2e 100755 --- a/src/interface/admin.py +++ b/src/interface/admin.py @@ -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) diff --git a/src/interface/forms.py b/src/interface/forms.py new file mode 100644 index 0000000..1807b14 --- /dev/null +++ b/src/interface/forms.py @@ -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 diff --git a/src/interface/models.py b/src/interface/models.py index cdcd89b..04cc090 100755 --- a/src/interface/models.py +++ b/src/interface/models.py @@ -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) diff --git a/src/interface/static/admin/js/vendor/select2/i18n/el.js b/src/interface/static/admin/js/vendor/select2/i18n/el.js index 46503bc..1f68e52 100644 --- a/src/interface/static/admin/js/vendor/select2/i18n/el.js +++ b/src/interface/static/admin/js/vendor/select2/i18n/el.js @@ -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}})(); \ No newline at end of file +(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}})(); diff --git a/src/interface/static/img/inspectocat.jpg b/src/interface/static/img/inspectocat.jpg new file mode 100644 index 0000000..dc119ac Binary files /dev/null and b/src/interface/static/img/inspectocat.jpg differ diff --git a/src/interface/templates/index.html b/src/interface/templates/index.html index ffdf0c7..d6a983c 100755 --- a/src/interface/templates/index.html +++ b/src/interface/templates/index.html @@ -26,12 +26,6 @@
- -
- - -
-
    @@ -43,10 +37,16 @@ - -
- -
+ +
+ +
 sort @@ -88,7 +88,7 @@ {% elif book.description == None %}
  • - We were unable to find a description in this books metadata. + We were unable to find a description.
    Have some instead?
  • diff --git a/src/interface/templates/login.html b/src/interface/templates/login.html new file mode 100644 index 0000000..aec5102 --- /dev/null +++ b/src/interface/templates/login.html @@ -0,0 +1,23 @@ + + + + + + + +

    Login

    +
    + {% csrf_token %} + {% for field in form %} +
    + {{ field.errors }} + {{ field.label_tag }} {{ field }} + {% if field.help_text %} +

    {{ field.help_text|safe }}

    + {% endif %} +
    + {% endfor %} + +
    + + \ No newline at end of file diff --git a/src/interface/templates/registration/login.html b/src/interface/templates/registration/login.html new file mode 100644 index 0000000..ba8ae1e --- /dev/null +++ b/src/interface/templates/registration/login.html @@ -0,0 +1,22 @@ + + + + + + + +

    Login

    +
    + {% for field in form %} +
    + {{ field.errors }} + {{ field.label_tag }} {{ field }} + {% if field.help_text %} +

    {{ field.help_text|safe }}

    + {% endif %} +
    + {% endfor %} + +
    + + \ No newline at end of file diff --git a/src/interface/templates/signup.html b/src/interface/templates/signup.html new file mode 100644 index 0000000..726d50b --- /dev/null +++ b/src/interface/templates/signup.html @@ -0,0 +1,26 @@ + + + + + Title + + +

    Sign up

    +
    + {% csrf_token %} + {% for field in form %} +

    + {{ field.label_tag }}
    + {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% for error in field.errors %} +

    {{ error }}

    + {% endfor %} +

    + {% endfor %} + +
    + + \ No newline at end of file diff --git a/src/interface/views.py b/src/interface/views.py index fde706a..19405e0 100755 --- a/src/interface/views.py +++ b/src/interface/views.py @@ -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, }