diff --git a/.gitignore b/.gitignore index d58e0b8..e0e05b5 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ *.iml + +/media diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3748cdc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.autopep8", + "editor.formatOnSave": true + } +} diff --git a/dashboard/views.py b/dashboard/views.py index a394363..5368ae7 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -1,55 +1,64 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect from django.views import View - class ViewDashboardView(View): - - def get(self,request): - return render(request,"dashboard.html") - + + def get(self, request): + if request.user.is_authenticated and self.request.user.is_superuser: + return render(request, "dashboard.html") + return redirect('users:login') + + class ViewAdminMenu(View): - - def get(self,request): - return render(request,"menu/admin-menu.html") - + + def get(self, request): + return render(request, "menu/admin-menu.html") + + class ViewAddMenuView(View): - - def get(self,request): - return render(request,"menu/add-menu.html") + + def get(self, request): + return render(request, "menu/add-menu.html") + class ViewUpdateMenuView(View): - - def get(self,request,pk): - return render(request,"menu/update-menu.html") - + + def get(self, request, pk): + return render(request, "menu/update-menu.html") + + class ViewAdminBranchs(View): - - def get(self,request): - return render(request,"branch/admin-branch.html") - + + def get(self, request): + return render(request, "branch/admin-branch.html") + + class ViewAddBranchView(View): - - def get(self,request): - return render(request,"branch/add-branch.html") + + def get(self, request): + return render(request, "branch/add-branch.html") + class ViewUpdateBranchView(View): - - def get(self,request,pk): - return render(request,"branch/update-branch.html") - + + def get(self, request, pk): + return render(request, "branch/update-branch.html") + + class ViewOrder(View): - - def get(self,request): - return render(request,"order/order-management.html") - + + def get(self, request): + return render(request, "order/order-management.html") + + class ViewOrderDetails(View): - - def get(self,request): - return render(request,"order/order-details.html") - + + def get(self, request): + return render(request, "order/order-details.html") + + class ViewOrderAfterStatus(View): - - def get(self,request,pk): - return render(request,"order/order-after-status.html") - \ No newline at end of file + + def get(self, request, pk): + return render(request, "order/order-after-status.html") diff --git a/media/users/default.jpg b/media/users/default.jpg new file mode 100644 index 0000000..b9a3a8b Binary files /dev/null and b/media/users/default.jpg differ diff --git a/requirements.txt b/requirements.txt index 825e832..c55c304 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ psycopg==3.1.18 psycopg-binary==3.1.18 sqlparse==0.4.4 typing_extensions==4.11.0 +tzdata==2024.1 whitenoise==6.6.0 diff --git a/templates/base-dashboard.html b/templates/base-dashboard.html index b8e1148..adbaa53 100644 --- a/templates/base-dashboard.html +++ b/templates/base-dashboard.html @@ -53,7 +53,10 @@

- Sign out +
+ {% csrf_token %} + +
diff --git a/templates/base.html b/templates/base.html index 9a9cf9d..ba57ba8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -57,14 +57,22 @@

Yummy

  • Branches
  • About
  • Contact
  • + {% if user.is_authenticated %}
  • My Profile
  • + {% endif %} - Sign In - {% comment %} Sign Out {% endcomment %} + {% if user.is_authenticated %} +
    + {% csrf_token %} + +
    + {% else %} + Sign In + {% endif %} @@ -145,4 +153,4 @@

    Our Resturant

    - \ No newline at end of file + diff --git a/users/admin.py b/users/admin.py index 8c38f3f..d914f1f 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,3 +1,4 @@ from django.contrib import admin +from .models import Profile -# Register your models here. +admin.site.register(Profile) diff --git a/users/forms.py b/users/forms.py new file mode 100644 index 0000000..1409f12 --- /dev/null +++ b/users/forms.py @@ -0,0 +1,156 @@ +from django import forms +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm, AuthenticationForm +from django.core.exceptions import ValidationError +from .models import Profile + + +class RegisterForm(UserCreationForm): + # fields we want to include and customize in our form + first_name = forms.CharField( + max_length=100, + required=True, + widget=forms.TextInput( + attrs={ + "placeholder": "First Name", + } + ), + ) + last_name = forms.CharField( + max_length=100, + required=True, + widget=forms.TextInput( + attrs={ + "placeholder": "Last Name", + } + ), + ) + username = forms.CharField( + max_length=100, + required=True, + widget=forms.TextInput( + attrs={ + "placeholder": "Username", + } + ), + ) + email = forms.EmailField( + required=True, + widget=forms.TextInput( + attrs={ + "placeholder": "Email", + } + ), + ) + password1 = forms.CharField( + max_length=50, + required=True, + widget=forms.PasswordInput( + attrs={ + "placeholder": "Password", + "data-toggle": "password", + "id": "password", + } + ), + ) + password2 = forms.CharField( + max_length=50, + required=True, + widget=forms.PasswordInput( + attrs={ + "placeholder": "Confirm Password", + "data-toggle": "password", + "id": "password", + } + ), + ) + + def clean_email(self): + email = self.cleaned_data.get('email') + if User.objects.filter(email=email).exists(): + raise ValidationError("This email is already registered.") + return email + + class Meta: + model = User + fields = [ + "first_name", + "last_name", + "username", + "email", + "password1", + "password2", + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['username'].widget.attrs.update({'autofocus': False}) + + +class LoginForm(AuthenticationForm): + username = forms.CharField( + max_length=100, + required=True, + widget=forms.TextInput(attrs={"placeholder": "Username"}), + ) + password = forms.CharField( + max_length=50, + required=True, + widget=forms.PasswordInput( + attrs={ + "placeholder": "Password", + "data-toggle": "password", + "id": "password", + "name": "password", + } + ), + ) + remember_me = forms.BooleanField(required=False) + + class Meta: + model = User + fields = ["username", "password"] + + +class UserUpdateForm(forms.ModelForm): + email = forms.EmailField( + required=True, widget=forms.TextInput(attrs={"class": "form-control"}) + ) + + first_name = forms.CharField( + max_length=100, + required=True, + widget=forms.TextInput( + attrs={ + "placeholder": "First Name", + "class": "form-control", + } + ), + ) + last_name = forms.CharField( + max_length=100, + required=False, + widget=forms.TextInput( + attrs={ + "placeholder": "Last Name", + "class": "form-control", + } + ), + ) + + class Meta: + model = User + fields = ['first_name', 'last_name', 'email'] + + +class UpdateProfileForm(forms.ModelForm): + avatar = forms.ImageField( + widget=forms.FileInput(attrs={"class": "form-control-file"}) + ) + address = forms.CharField( + widget=forms.Textarea(attrs={"class": "form-control", "rows": 5}) + ) + + class Meta: + model = Profile + fields = ["avatar", "address"] diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py new file mode 100644 index 0000000..e683f95 --- /dev/null +++ b/users/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.4 on 2024-05-12 05:27 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('avatar', models.ImageField(default='users/default.jpg', upload_to='users')), + ('address', models.TextField()), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/users/models.py b/users/models.py index 71a8362..fa88b22 100644 --- a/users/models.py +++ b/users/models.py @@ -1,3 +1,19 @@ from django.db import models +from django.contrib.auth.models import User -# Create your models here. + +# Extending User Model Using a One-To-One Link +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + + avatar = models.ImageField( + default='users/default.jpg', upload_to='users') + + address = models.TextField() + + def __str__(self): + return self.user.username + + # resizing images + def save(self, *args, **kwargs): + super().save() diff --git a/users/signals.py b/users/signals.py new file mode 100644 index 0000000..2231563 --- /dev/null +++ b/users/signals.py @@ -0,0 +1,16 @@ +from django.db.models.signals import post_save +from django.contrib.auth.models import User +from django.dispatch import receiver + +from .models import Profile + + +@receiver(post_save, sender=User) +def create_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + + +@receiver(post_save, sender=User) +def save_profile(sender, instance, **kwargs): + instance.profile.save() diff --git a/users/static/register.css b/users/static/register.css new file mode 100644 index 0000000..5a347f3 --- /dev/null +++ b/users/static/register.css @@ -0,0 +1,46 @@ +.signup-container { + max-width: 400px; + margin: 0 auto; + padding: 20px; + background: rgba(12, 11, 9, 0.6); + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin-top: 100px; + padding-right: 20px; + padding-left: 20px; +} + +.signup-container h2 { + text-align: center; + margin-bottom: 20px; +} + +.signup-form input[type="text"], +.signup-form input[type="password"], +.signup-form input[type="email"], +.signup-form input[type="submit"] { + width: calc(100% - 20px); + padding: 10px; + margin-bottom: 15px; + border: none; + border-radius: 5px; + background-color: #1a1814; + color: #fff; + box-sizing: border-box; +} + +.signup-form input[type="submit"] { + background-color: #cda45e; + color: #1a1814; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.signup-form input[type="submit"]:hover { + background-color: #d9ba85; +} + +.signup-form p { + margin-top: 15px; + text-align: center; +} diff --git a/users/static/users.css b/users/static/users.css index 4df0d26..18291d2 100644 --- a/users/static/users.css +++ b/users/static/users.css @@ -3,7 +3,8 @@ h2 { } .btn-primary { - background-color: #cda45e; /* Green */ + background-color: #cda45e; + /* Green */ border: none; color: white; padding: 10px 24px; @@ -22,7 +23,8 @@ h2 { } .btn-secondary { - background-color: #b4b3b0; /* Green */ + background-color: #b4b3b0; + /* Green */ border: none; color: white; padding: 10px 24px; @@ -39,3 +41,55 @@ h2 { .btn-secondary:hover { background-color: #f19c09; } + +/* login page */ + +.login-container { + max-width: 400px; + margin: 0 auto; + padding: 20px; + background: rgba(12, 11, 9, 0.6); + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin-top: 100px; + padding-right: 20px; + /* Added padding */ + padding-left: 20px; + /* Added padding */ +} + +.login-container h2 { + text-align: center; + margin-bottom: 20px; +} + +.login-form input[type="text"], +.login-form input[type="password"], +.login-form input[type="submit"] { + width: calc(100% - 20px); + /* Adjusted width */ + padding: 10px; + margin-bottom: 15px; + border: none; + border-radius: 5px; + background-color: #1a1814; + color: #fff !important; + box-sizing: border-box; + /* Include padding and border in width calculation */ +} + +.login-form input[type="submit"] { + background-color: #cda45e; + color: #1a1814; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.login-form input[type="submit"]:hover { + background-color: #d9ba85; +} + +.login-form p { + margin-top: 15px; + text-align: center; +} diff --git a/users/templates/.gitkeep b/users/templates/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/templates/edit-profile.html b/users/templates/edit-profile.html index 64ebfac..ab0cbd1 100644 --- a/users/templates/edit-profile.html +++ b/users/templates/edit-profile.html @@ -3,53 +3,60 @@ {% load static %} {% block css_files %} + {% endblock %} {% block content %} - - + + {% endblock %} diff --git a/users/templates/login.html b/users/templates/login.html new file mode 100644 index 0000000..cad25d0 --- /dev/null +++ b/users/templates/login.html @@ -0,0 +1,47 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block css_files %} + + + +{% endblock %} + +{% block content %} + + + +{% endblock %} diff --git a/users/templates/register.html b/users/templates/register.html new file mode 100644 index 0000000..cdcd308 --- /dev/null +++ b/users/templates/register.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block css_files %} + + + +{% endblock %} + +{% block content %} + + + +{% endblock %} diff --git a/users/templates/user-profile.html b/users/templates/user-profile.html index e5e9133..0e2f293 100644 --- a/users/templates/user-profile.html +++ b/users/templates/user-profile.html @@ -3,42 +3,45 @@ {% load static %} {% block content %} - + {% endblock %} diff --git a/users/urls.py b/users/urls.py index cce344f..3d4b6a3 100644 --- a/users/urls.py +++ b/users/urls.py @@ -1,11 +1,33 @@ from django.urls import path from . import views +from .views import home, RegisterView, CustomLoginView, update_user +from django.contrib.auth import views as auth_views -app_name = 'users' +from .forms import LoginForm + +app_name = "users" urlpatterns = [ - path('profile-management', views.ViewUserProfileView.as_view(), name='view-profile'), - path('edit-management', views.ViewEditProfileView.as_view(), name='edit-profile'), + path( + "profile-management", views.ViewUserProfileView.as_view(), name="view-profile" + ), + + path("edit-management", update_user, name="edit-profile"), + + + path("register/", RegisterView.as_view(), name="users-register"), + + path( + "login/", + CustomLoginView.as_view( + redirect_authenticated_user=True, + template_name="login.html", + authentication_form=LoginForm, + ), + name="login", + ), + + path("logout/", auth_views.LogoutView.as_view(), name="logout"), ] diff --git a/users/views.py b/users/views.py index c08541a..daf79c3 100644 --- a/users/views.py +++ b/users/views.py @@ -1,14 +1,92 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.views import LoginView from django.views import View +from django.contrib.auth.decorators import login_required +from .forms import UserUpdateForm +from .forms import RegisterForm, LoginForm, UpdateProfileForm +from .models import Profile +from django.contrib.auth.models import User +from .forms import UserUpdateForm +import os +from django.contrib import messages + + +def home(request): + return render(request, 'users/home.html') + + +class RegisterView(View): + form_class = RegisterForm + initial = {'key': 'value'} + template_name = 'register.html' + + def dispatch(self, request, *args, **kwargs): + # will redirect to the home page if a user tries to access the register page while logged in + if request.user.is_authenticated: + return redirect(to='/') + + # else process dispatch as it otherwise normally would + return super(RegisterView, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + form = self.form_class(initial=self.initial) + return render(request, self.template_name, {'form': form}) + + def post(self, request, *args, **kwargs): + form = self.form_class(request.POST) + + if form.is_valid(): + form.save() + messages.success(request, 'Registration successful!') + return redirect('users:login') + + return render(request, self.template_name, {'form': form}) + + +# Class based view that extends from the built in login view to add a remember me functionality +class CustomLoginView(LoginView): + form_class = LoginForm + + def get_success_url(self): + # redirect based on the user type + if self.request.user.is_superuser: + return '/dashboard' + return '/' + + +@login_required +def update_user(request): + Profile.objects.get_or_create(user=request.user) + user = get_object_or_404(User, pk=request.user.id) + if request.user != user: + # Redirect if the logged-in user is not the same as the user to be edited + return redirect(to='users:view-profile') + + if request.method == 'POST': + form = UserUpdateForm(request.POST, instance=user) + profile_form = UpdateProfileForm( + request.POST, request.FILES, instance=request.user.profile) + if form.is_valid() and profile_form.is_valid(): + + profile, created = Profile.objects.get_or_create(user=request.user) + image_path = profile.avatar.path + + if 'avatar' in request.FILES: + if os.path.exists(image_path): + os.remove(image_path) + form.save() + profile_form.save() + + # Redirect to a success page + return redirect(to='users:view-profile') + else: + form = UserUpdateForm(instance=user) + profile_form = UpdateProfileForm(instance=request.user.profile) + + return render(request, 'edit-profile.html', {'form': form, 'profile_form': profile_form}) class ViewUserProfileView(View): - - def get(self,request): - return render(request,"user-profile.html") - -class ViewEditProfileView(View): - - def get(self,request): - return render(request,"edit-profile.html") - \ No newline at end of file + + def get(self, request): + return render(request, "user-profile.html") diff --git a/yummy/settings.py b/yummy/settings.py index 43291c1..c60e696 100644 --- a/yummy/settings.py +++ b/yummy/settings.py @@ -9,7 +9,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.0/ref/settings/ """ - +import os import environ from pathlib import Path @@ -143,6 +143,29 @@ BASE_DIR / "static" ] +# added by mash + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = '/media/' + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +# ended by mash + +# added by naqibullah : if user is admin after log + + +# if user is customer +LOGIN_REDIRECT_URL = '/' + +# if the user is admin ? +# LOGIN_REDIRECT_URL = '/dashboard' + + +LOGIN_URL = 'users/login' +LOGOUT_REDIRECT_URL = '/' + +# added by naqibullah finished + # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/yummy/urls.py b/yummy/urls.py index b6e7fdb..a6fe6ba 100644 --- a/yummy/urls.py +++ b/yummy/urls.py @@ -18,13 +18,26 @@ from django.contrib import admin from django.urls import include, path +from django.urls import path, include +from django.conf import settings + +from django.conf.urls.static import static + +from django.contrib.auth import views as auth_views +from users.views import CustomLoginView + +from users.forms import LoginForm + urlpatterns = [ path('', views.index, name='index'), path('admin/', admin.site.urls), path('dashboard/', include(('dashboard.urls', 'dashboard'), namespace='dashboard')), - path('users/', include(('users.urls', 'users'), namespace='users')), - path('restaurants/', include(('restaurants.urls', 'restaurants'), namespace='restaurants')), + path('users/', include('users.urls')), + path('restaurants/', include(('restaurants.urls', + 'restaurants'), namespace='restaurants')), path('menus/', include(('menus.urls', 'menus'), namespace='menus')), path('orders/', include(('orders.urls', 'orders'), namespace='orders')), path('contacts/', include(('contacts.urls', 'contacts'), namespace='contacts')), -] + +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +# ended by mash