-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #33 from jamedadi/develop
Develop
- Loading branch information
Showing
173 changed files
with
5,269 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,48 @@ | ||
Yummy | ||
--- | ||
**This project is going to be a clone of sanpp food with Python and Django framework.** | ||
|
||
--- | ||
|
||
## What is yummy? | ||
|
||
It’s actually a service which users can order food very easily and restaurant managers can register their services so | ||
that other people can use them. | ||
|
||
As I said this is going to be a clone of this service , clearly It’s not going to implement all the features | ||
|
||
--- | ||
|
||
## The goal of this project: | ||
|
||
The main purpose of this project is being an acceptable resume and also a good practice of Django framework | ||
|
||
--- | ||
|
||
## Features: | ||
|
||
- [x] We will have two type of users in this project : 1 - Customer 2 - Service Provider. | ||
- [x] Each service provider can provide different services such as (restaurant, fast food, confectionery, supermarket, | ||
…). | ||
- [x] Each service will be able to have a menu containing different items. | ||
- [x] Each service will be able to have custom categories for the items of the menu. | ||
- [x] Each service provider must specify the supported areas for the item delivery for the service. | ||
- [ ] Each service provider can add discount on some of their items for a limited time. | ||
- [x] Each service will have active days and hours. | ||
- [x] Each customer can have different addresses. | ||
- [x] Each customer will be able to see the services(only the supported services in their area). | ||
- [x] Each customer can add items to their cart(note : Each cart can only contain items from one specific service, that | ||
means adding items from different services causes multiple carts). | ||
- [ ] Each customer will be able to add comment for the items which was in their cart after the order was delivered( | ||
note: customers will be able to add one comment for an item after each successful order). | ||
- [ ] Each item will have a score based on its comments. | ||
- [x] Each user will be to see the status of the order after the payment has been successful. | ||
- [ ] The quantity of each item must be increased and decreased at successful orders. | ||
|
||
--- | ||
|
||
## Architecture of the project: | ||
|
||
The project is based on the MVT architecture of the Django framework, so we will use SSR(server side rendering) | ||
|
||
--- |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from django.contrib import admin | ||
from django.contrib.auth.hashers import make_password | ||
|
||
from accounts.models import Customer, ServiceProvider | ||
|
||
|
||
@admin.register(Customer) | ||
class CustomerAdmin(admin.ModelAdmin): | ||
list_display = ('phone_number', 'first_name', 'last_name', 'date_joined', 'is_active') | ||
list_filter = ('is_active', 'date_joined') | ||
search_fields = ('phone_number',) | ||
|
||
def save_model(self, request, obj, form, change): | ||
obj.password = make_password(form.cleaned_data['password']) | ||
return super().save_model(request, obj, form, change) | ||
|
||
|
||
@admin.register(ServiceProvider) | ||
class ServiceProviderAdmin(admin.ModelAdmin): | ||
list_display = ('username', 'email', 'phone_number', 'date_joined', 'is_active') | ||
list_filter = ('is_active', 'date_joined') | ||
search_fields = ('username', 'phone_number') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class AccountsConfig(AppConfig): | ||
default_auto_field = 'django.db.models.BigAutoField' | ||
name = 'accounts' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from django.contrib.auth.backends import BaseBackend | ||
|
||
from accounts.models import Customer, ServiceProvider | ||
|
||
|
||
class PhoneNumberPasswordBackend(BaseBackend): | ||
def authenticate(self, request, phone_number=None, password=None): | ||
try: | ||
customer = Customer.objects.get(phone_number=phone_number) | ||
except Customer.DoesNotExist: | ||
return None | ||
else: | ||
if password: # if the password is sent via the form | ||
if customer.check_password(password): | ||
return customer | ||
else: | ||
return None | ||
return customer | ||
|
||
def get_user(self, user_id): | ||
try: | ||
customer = Customer.objects.get(pk=user_id) | ||
except Customer.DoesNotExist: | ||
return None | ||
else: | ||
return customer | ||
|
||
|
||
class ServiceProviderAuthentication(BaseBackend): | ||
def authenticate(self, request, username=None, password=None): | ||
try: | ||
user = ServiceProvider.objects.get(username=username, password=password) | ||
return user | ||
|
||
except ServiceProvider.DoesNotExist: | ||
return None | ||
|
||
def get_user(self, user_id): | ||
try: | ||
return ServiceProvider.objects.get(pk=user_id) | ||
except ServiceProvider.DoesNotExist: | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
from django import forms | ||
from django.contrib.auth import password_validation | ||
from django.core.exceptions import ValidationError | ||
from django.db.models import Q | ||
from django.core.validators import int_list_validator | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from accounts.models import ServiceProvider, Customer | ||
from accounts.utils import phone_number_validator | ||
|
||
|
||
class CustomerLoginRegisterForm(forms.Form): | ||
phone_number = forms.CharField(max_length=12, | ||
validators=[ | ||
int_list_validator(message=_('only digits are accepted')), | ||
phone_number_validator | ||
], | ||
widget=forms.TextInput( | ||
attrs={'class': 'form-control', 'placeholder': 'phone number'}) | ||
) | ||
|
||
|
||
class CustomerCodeConfirmForm(forms.Form): | ||
code = forms.CharField( | ||
validators=[int_list_validator(message=_('only digits are accepted'))], | ||
widget=forms.TextInput( | ||
attrs={ | ||
'class': 'form-control', | ||
'placeholder': _('confirmation code') | ||
} | ||
) | ||
) | ||
|
||
|
||
class CustomerPasswordForm(forms.Form): | ||
password = forms.CharField( | ||
widget=forms.PasswordInput( | ||
attrs={ | ||
'class': 'form-control', | ||
'placeholder': _('password') | ||
} | ||
) | ||
) | ||
|
||
|
||
class CustomerPasswordSetForm(forms.ModelForm): | ||
error_messages = { | ||
'password_mismatch': _('The two password fields didn’t match.'), | ||
} | ||
password = forms.CharField( | ||
label=_("Password"), | ||
strip=False, | ||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), | ||
help_text=password_validation.password_validators_help_text_html(), | ||
) | ||
password2 = forms.CharField( | ||
label=_("Password confirmation"), | ||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), | ||
strip=False, | ||
help_text=_("Enter the same password as before, for verification."), | ||
) | ||
|
||
class Meta: | ||
model = Customer | ||
fields = ('password',) | ||
|
||
def clean_password2(self): | ||
password = self.cleaned_data.get("password") | ||
password2 = self.cleaned_data.get("password2") | ||
if password and password2 and password != password2: | ||
raise ValidationError( | ||
self.error_messages['password_mismatch'], | ||
code='password_mismatch', | ||
) | ||
return password2 | ||
|
||
def save(self, commit=True): | ||
customer = super().save(commit=False) | ||
customer.set_password(self.cleaned_data["password"]) | ||
if commit: | ||
customer.save() | ||
return customer | ||
|
||
|
||
class CustomerProfileUpdateForm(forms.ModelForm): | ||
class Meta: | ||
model = Customer | ||
fields = ('first_name', 'last_name') | ||
|
||
|
||
class ServiceProviderRegistrationForm(forms.ModelForm): | ||
username = forms.CharField( | ||
max_length=30, | ||
min_length=4, | ||
widget=forms.TextInput( | ||
attrs={ | ||
'placeholder': 'Username', | ||
'class': 'form-control'} | ||
) | ||
) | ||
phone_number = forms.CharField(max_length=12, | ||
validators=[ | ||
int_list_validator(message=_('only digits are accepted')), | ||
phone_number_validator | ||
], | ||
widget=forms.TextInput( | ||
attrs={'class': 'form-control', 'placeholder': 'phone number'}) | ||
) | ||
confirm_password = forms.CharField( | ||
widget=forms.PasswordInput( | ||
attrs={ | ||
'placeholder': 'Confirm Password', | ||
'class': 'form-control'} | ||
) | ||
) | ||
|
||
class Meta: | ||
model = ServiceProvider | ||
fields = ('username', 'email', 'phone_number', 'password', 'confirm_password') | ||
widgets = { | ||
'email': forms.EmailInput(attrs={'placeholder': 'Email', 'class': 'form-control'}), | ||
'phone_number': forms.TextInput(attrs={'placeholder': 'Phone Number', 'class': 'form-control'}), | ||
'password': forms.PasswordInput(attrs={'placeholder': 'Password', 'class': 'form-control'}), | ||
} | ||
|
||
def clean_confirm_password(self): | ||
if self.cleaned_data['password'] != self.cleaned_data['confirm_password']: | ||
raise ValidationError('passwords not equal!') | ||
return self.cleaned_data['confirm_password'] | ||
|
||
|
||
class ServiceProviderLoginForm(forms.Form): | ||
username = forms.CharField( | ||
min_length=4, | ||
widget=forms.TextInput( | ||
attrs={ | ||
'placeholder': 'Username, Email, Phone Number', | ||
'class': 'form-control'} | ||
) | ||
) | ||
|
||
password = forms.CharField( | ||
max_length=30, | ||
min_length=4, | ||
widget=forms.PasswordInput( | ||
attrs={ | ||
'placeholder': 'Password', | ||
'class': 'form-control'} | ||
) | ||
) | ||
|
||
def clean(self): | ||
cleaned_data = super().clean() | ||
|
||
username = cleaned_data['username'] | ||
user = ServiceProvider.objects.filter( | ||
Q(username=username) | | ||
Q(email=username) | | ||
Q(phone_number=username), | ||
).first() | ||
|
||
if user and user.check_password(cleaned_data['password']): | ||
cleaned_data['user'] = user | ||
return cleaned_data | ||
|
||
raise ValidationError('username or password invalid!') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Generated by Django 3.2 on 2021-08-28 20:31 | ||
|
||
import accounts.models | ||
import django.contrib.auth.models | ||
import django.contrib.auth.validators | ||
from django.db import migrations, models | ||
import django.utils.timezone | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='Customer', | ||
fields=[ | ||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('password', models.CharField(max_length=128, verbose_name='password')), | ||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | ||
('phone_number', models.CharField(max_length=13, unique=True, verbose_name='phone number')), | ||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), | ||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), | ||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), | ||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), | ||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), | ||
], | ||
options={ | ||
'verbose_name': 'Customer', | ||
'verbose_name_plural': 'Customers', | ||
'db_table': 'customer', | ||
}, | ||
managers=[ | ||
('objects', accounts.models.CustomerManager()), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='ServiceProvider', | ||
fields=[ | ||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('password', models.CharField(max_length=128, verbose_name='password')), | ||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | ||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), | ||
('email', models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email address')), | ||
('phone_number', models.CharField(max_length=13, unique=True, verbose_name='phone number')), | ||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), | ||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), | ||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), | ||
], | ||
options={ | ||
'verbose_name': 'Service provider', | ||
'verbose_name_plural': 'Service providers', | ||
'db_table': 'service_provider', | ||
}, | ||
managers=[ | ||
('objects', django.contrib.auth.models.UserManager()), | ||
], | ||
), | ||
] |
Oops, something went wrong.