Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Master #4

Merged
merged 60 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
b87c57d
Update
Nov 21, 2024
4a556ef
Update
Nov 21, 2024
53c91b8
Update
Nov 21, 2024
4025c5c
Update
Nov 21, 2024
5fc6d07
Update
Nov 21, 2024
b2ee11a
Update
Nov 21, 2024
718c2ba
Update
Nov 21, 2024
b4082ed
Update
Nov 21, 2024
626fd7e
Update
Nov 21, 2024
7ffbbdb
Update
Nov 21, 2024
f9b2e04
Update
Nov 21, 2024
9cdd8e7
Update
Nov 21, 2024
8d210d2
Update
Nov 21, 2024
8bae620
Update
Nov 21, 2024
a9e6eb9
Update
Nov 21, 2024
776ac91
Update
Nov 21, 2024
2211c03
Update
Nov 21, 2024
d428f2b
Update
Nov 21, 2024
bd8bb91
Update
Nov 21, 2024
b6c0fb5
Update
Nov 21, 2024
9afc581
Update
Nov 21, 2024
96d74b8
Update
Nov 21, 2024
73b8501
Update
Nov 21, 2024
2f9f318
Update
Nov 21, 2024
d856fd0
Update
Nov 21, 2024
3fd8a76
Update
Nov 21, 2024
3bc86c5
Update
Nov 21, 2024
491e90e
Update
Nov 21, 2024
4f512d6
Update
Nov 21, 2024
c6b5060
Update
Nov 21, 2024
ff1156b
Update
Nov 21, 2024
3e4e6f7
Update
Nov 21, 2024
c3a1140
Update
Nov 21, 2024
8f43d1d
Update
Nov 21, 2024
2bfab95
Update
Nov 21, 2024
b96d7fb
Update
Nov 21, 2024
bf9b421
Update
Nov 21, 2024
1eb3b0f
Update
Nov 21, 2024
481aedd
Update
Nov 21, 2024
7e63608
Update
Nov 21, 2024
8442f8a
Update
Nov 21, 2024
e8618ba
Update
Nov 21, 2024
097acd0
Update
Nov 21, 2024
6cb98da
Update
Nov 21, 2024
1774930
Update
Nov 21, 2024
b22ac8e
Update
Nov 21, 2024
71c6784
Update
Nov 21, 2024
efcbcab
Update
Nov 21, 2024
605162d
Update
Nov 21, 2024
15c9e14
Update
Nov 21, 2024
d58edc7
Update
Nov 21, 2024
8b3ba3e
Update
Nov 21, 2024
2396f42
Update
Nov 21, 2024
86b0114
Update
Nov 21, 2024
e8d9dae
Initial commit: Add dropship project files
Nov 22, 2024
17c8b66
Merge remote master branch with local changes
Nov 22, 2024
ba94eb1
Merge local_changes branch into master
Nov 22, 2024
352625a
Add session summary
Nov 22, 2024
8e8714e
Update session summary with to-do items
Nov 22, 2024
3a67fda
Update session summary with to-do items
Nov 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
500 changes: 500 additions & 0 deletions .bash_history

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
121 changes: 84 additions & 37 deletions dropship_project/admin.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,94 @@

import csv
from django.contrib import admin
from django.http import HttpResponse
from .models import Category, Product, Order, CartItem
from django.contrib.auth.admin import UserAdmin
from django.db.models import Count, F
from django.utils import timezone
from django.urls import path
from django.shortcuts import render
from .models import CustomUser, Product, Order, CartItem
from .admin_filters import DateRangeFilter
from .admin_widgets import QuickLinksWidget, RecentOrdersWidget, SalesStatisticsWidget
import json

class CustomAdminSite(admin.AdminSite):
site_header = 'Dropship Admin'
site_title = 'Dropship Admin Portal'
index_title = 'Welcome to Dropship Admin Portal'
login_template = 'admin/login.html'
index_template = 'admin/dashboard.html'

def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('order-stats/', self.admin_view(self.order_stats_view), name='order_stats'),
]
return custom_urls + urls

def index(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context['quick_links'] = QuickLinksWidget()
extra_context['recent_orders'] = RecentOrdersWidget()
extra_context['sales_statistics'] = SalesStatisticsWidget()
return super().index(request, extra_context)

def order_stats_view(self, request):
end_date = timezone.now().date()
start_date = end_date - timezone.timedelta(days=6)
order_data = Order.objects.filter(created_at__date__range=[start_date, end_date]) .values('created_at__date') .annotate(count=Count('id')) .order_by('created_at__date')

labels = [(start_date + timezone.timedelta(days=i)).strftime('%Y-%m-%d') for i in range(7)]
data = [0] * 7

for item in order_data:
index = (item['created_at__date'] - start_date).days
data[index] = item['count']

class CartItemInline(admin.TabularInline):
model = CartItem
extra = 0
context = {
'labels': json.dumps(labels),
'data': json.dumps(data),
}
return render(request, 'admin/order_stats.html', context)

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug')
prepopulated_fields = {'slug': ('name',)}
admin_site = CustomAdminSite(name='customadmin')

@admin.register(Product)
@admin.register(CustomUser, site=admin_site)
class CustomUserAdmin(UserAdmin):
list_display = ('username', 'email', 'is_staff', 'is_active')
list_filter = ('is_staff', 'is_active')
fieldsets = UserAdmin.fieldsets + (
('Additional Info', {'fields': ('phone_number', 'address')}),
)

@admin.register(Product, site=admin_site)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'category', 'cost_price', 'selling_price', 'profit_margin')
list_filter = ('category', 'name')
list_display = ('name', 'price', 'stock', 'category')
list_filter = ('category',)
search_fields = ('name', 'description')
actions = ['increase_stock', 'decrease_stock']

def increase_stock(self, request, queryset):
updated = queryset.update(stock=F('stock') + 10)
self.message_user(request, f'Successfully increased stock for {updated} products.')
increase_stock.short_description = "Increase stock by 10"

@admin.register(CartItem)
def decrease_stock(self, request, queryset):
updated = queryset.update(stock=F('stock') - 10)
self.message_user(request, f'Successfully decreased stock for {updated} products.')
decrease_stock.short_description = "Decrease stock by 10"

@admin.register(Order, site=admin_site)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'total_price', 'status', 'created_at')
list_filter = ('status', DateRangeFilter)
search_fields = ('user__username', 'user__email')
actions = ['mark_as_shipped']

def mark_as_shipped(self, request, queryset):
updated = queryset.update(status='shipped', shipped_at=timezone.now())
self.message_user(request, f'{updated} orders were successfully marked as shipped.')
mark_as_shipped.short_description = "Mark selected orders as shipped"

@admin.register(CartItem, site=admin_site)
class CartItemAdmin(admin.ModelAdmin):
list_display = ('user', 'product', 'quantity', 'total_price')
list_display = ('user', 'product', 'quantity')
list_filter = ('user', 'product')

@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('user', 'total_price', 'created_at', 'status', 'is_paid')
list_filter = ('status', 'created_at')
search_fields = ('user__username',)
inlines = [CartItemInline]
actions = ['export_to_csv']

def is_paid(self, obj):
return obj.is_paid
is_paid.boolean = True

def export_to_csv(self, request, queryset):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="orders.csv"'
writer = csv.writer(response)
writer.writerow(['Order ID', 'User', 'Total Price', 'Created At', 'Status', 'Is Paid'])
for order in queryset:
writer.writerow([order.id, order.user.username, order.total_price, order.created_at, order.status, order.is_paid])
return response
export_to_csv.short_description = "Export selected orders to CSV"
36 changes: 36 additions & 0 deletions dropship_project/admin_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django.contrib import admin
from django.utils import timezone
from datetime import datetime, timedelta

class DateRangeFilter(admin.SimpleListFilter):
title = 'Date Range'
parameter_name = 'date_range'

def lookups(self, request, model_admin):
return (
('today', 'Today'),
('yesterday', 'Yesterday'),
('this_week', 'This week'),
('last_week', 'Last week'),
('this_month', 'This month'),
('last_month', 'Last month'),
)

def queryset(self, request, queryset):
if self.value() == 'today':
return queryset.filter(created_at__date=timezone.now().date())
if self.value() == 'yesterday':
return queryset.filter(created_at__date=timezone.now().date() - timedelta(days=1))
if self.value() == 'this_week':
return queryset.filter(created_at__date__gte=timezone.now().date() - timedelta(days=7))
if self.value() == 'last_week':
start_of_last_week = timezone.now().date() - timedelta(days=14)
end_of_last_week = timezone.now().date() - timedelta(days=7)
return queryset.filter(created_at__date__range=[start_of_last_week, end_of_last_week])
if self.value() == 'this_month':
return queryset.filter(created_at__date__gte=timezone.now().date().replace(day=1))
if self.value() == 'last_month':
last_month = timezone.now().date().replace(day=1) - timedelta(days=1)
start_of_last_month = last_month.replace(day=1)
return queryset.filter(created_at__date__range=[start_of_last_month, last_month])

57 changes: 57 additions & 0 deletions dropship_project/admin_widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.utils.html import format_html
from django.contrib.admin.widgets import AdminDateWidget
from django.forms.widgets import Media, MEDIA_TYPES

class QuickLinksWidget:
title = _('Quick Links')
template = 'admin/widgets/quick_links.html'

def __init__(self):
self.children = [
{
'title': _('Add New Product'),
'url': reverse('admin:dropship_project_product_add'),
},
{
'title': _('View Recent Orders'),
'url': reverse('admin:dropship_project_order_changelist') + '?status__exact=pending',
},
{
'title': _('User Statistics'),
'url': reverse('admin:dropship_project_customuser_changelist'),
},
]

class RecentOrdersWidget:
title = _('Recent Orders')
template = 'admin/widgets/recent_orders.html'

def __init__(self):
self.limit = 5

def get_context(self):
from .models import Order
return {
'orders': Order.objects.order_by('-created_at')[:self.limit]
}

class SalesStatisticsWidget:
title = _('Sales Statistics')
template = 'admin/widgets/sales_statistics.html'

def get_context(self):
from .models import Order
from django.db.models import Sum
from django.utils import timezone

today = timezone.now().date()
this_month = today.replace(day=1)

return {
'total_sales': Order.objects.aggregate(total=Sum('total_price'))['total'] or 0,
'today_sales': Order.objects.filter(created_at__date=today).aggregate(total=Sum('total_price'))['total'] or 0,
'month_sales': Order.objects.filter(created_at__date__gte=this_month).aggregate(total=Sum('total_price'))['total'] or 0,
}

58 changes: 22 additions & 36 deletions dropship_project/forms.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,35 @@
from django import forms
from django.contrib.auth.forms import PasswordChangeForm, UserCreationForm, AuthenticationForm
from django.contrib.auth import password_validation
from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV2Checkbox
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from .models import CustomUser

class CustomAuthenticationForm(AuthenticationForm):
remember_me = forms.BooleanField(required=False, initial=False)
captcha = ReCaptchaField(widget=ReCaptchaV2Checkbox)
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)

class CustomPasswordChangeForm(PasswordChangeForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['old_password'].widget.attrs.update({'class': 'form-control', 'placeholder': 'Current Password'})
self.fields['new_password1'].widget.attrs.update({'class': 'form-control', 'placeholder': 'New Password'})
self.fields['new_password2'].widget.attrs.update({'class': 'form-control', 'placeholder': 'Confirm New Password'})
class Meta:
model = CustomUser
fields = ('username', 'email', 'password1', 'password2')

def clean_new_password1(self):
password1 = self.cleaned_data.get('new_password1')
def clean_password1(self):
password1 = self.cleaned_data.get('password1')
try:
password_validation.validate_password(password1, self.user)
except forms.ValidationError as error:
self.add_error('new_password1', error)
validate_password(password1, self.instance)
except ValidationError as error:
self.add_error('password1', error)
return password1

class UserProfileForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = ['username', 'email', 'first_name', 'last_name', 'phone_number', 'address', 'date_of_birth']
widgets = {
'date_of_birth': forms.DateInput(attrs={'type': 'date'}),
}

class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
captcha = ReCaptchaField(widget=ReCaptchaV2Checkbox)
class CustomAuthenticationForm(AuthenticationForm):
remember_me = forms.BooleanField(required=False, initial=False)

class UserProfileForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = ('username', 'email', 'password1', 'password2', 'captcha')
fields = ('username', 'email', 'first_name', 'last_name')

def save(self, commit=True):
user = super(UserRegistrationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
def clean_email(self):
email = self.cleaned_data['email']
if CustomUser.objects.filter(email=email).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError('This email address is already in use.')
return email

Loading
Loading