Skip to content

Commit 0c519dd

Browse files
committed
feat: Added site deletion endpoint.
1 parent 3dbf26f commit 0c519dd

File tree

4 files changed

+146
-1
lines changed

4 files changed

+146
-1
lines changed

ecommerce/extensions/edly_ecommerce_app/api/v1/constants.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
'DJANGO_SETTINGS_UPDATE_FAILURE': _('Django settings update failed.'),
1414
'SITE_CONFIGURATIONS_UPDATE_SUCCESS': _('Site Configurations updated successfully.'),
1515
'SITE_CONFIGURATIONS_UPDATE_FAILURE': _('Site Configurations update failed.'),
16+
'SITE_DELETION_SUCCESS': _('Ecommerce site deletion was successful.'),
17+
'SITE_DELETION_FAILURE': _('Ecommerce site deletion failed.'),
1618
}
1719

1820
CLIENT_SITE_SETUP_FIELDS = [

ecommerce/extensions/edly_ecommerce_app/api/v1/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
url(r'site_themes/', views.SiteThemesActions.as_view(), name='site_themes'),
99
url(r'csrf_token/', views.CSRFTokenInfo.as_view(), name='get_csrf_token'),
1010
url(r'edly_sites/', views.EdlySiteViewSet.as_view(), name='edly_sites'),
11+
url(r'delete_site/', views.EdlySiteDeletionViewSet.as_view(), name='edly_delete_site'),
1112
url(r'edly_site_config/', views.EdlySiteConfigViewset.as_view(), name='edly_site_config'),
1213
]

ecommerce/extensions/edly_ecommerce_app/api/v1/views.py

+132-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from django.contrib.auth.decorators import login_required
66
from django.contrib.sites.models import Site
77
from django.contrib.sites.shortcuts import get_current_site
8+
from django.contrib.auth import get_user_model
9+
from django.db import transaction
810
from django.http import Http404
911
from django.middleware import csrf
1012
from django.utils.decorators import method_decorator
@@ -18,6 +20,7 @@
1820

1921
from ecommerce.core.constants import ENABLE_SUBSCRIPTIONS_ON_RUNTIME_SWITCH
2022
from ecommerce.core.models import SiteConfiguration
23+
from ecommerce.courses.models import Course
2124
from ecommerce.extensions.edly_ecommerce_app.api.v1.constants import ERROR_MESSAGES
2225
from ecommerce.extensions.edly_ecommerce_app.helpers import (
2326
user_is_course_creator,
@@ -27,9 +30,17 @@
2730
validate_site_configurations_for_self_service_api,
2831
validate_site_theme,
2932
)
30-
from ecommerce.extensions.edly_ecommerce_app.permissions import CanAccessSiteCreation
33+
from ecommerce.extensions.edly_ecommerce_app.permissions import CanAccessSiteCreation, CanAccessSiteDeletion
3134
from ecommerce.extensions.partner.models import Partner
3235
from ecommerce.theming.models import SiteTheme
36+
from ecommerce.extensions.partner.models import StockRecord
37+
from ecommerce.extensions.order.models import Order, Line
38+
from ecommerce.extensions.offer.models import ConditionalOffer
39+
from ecommerce.extensions.order.models import MarkOrdersStatusCompleteConfig
40+
import logging
41+
42+
logger = logging.getLogger(__name__)
43+
User = get_user_model()
3344

3445

3546
class SiteThemesActions(APIView):
@@ -205,6 +216,126 @@ def get_oauth2_credentials(self):
205216
)
206217
return oauth2_values
207218

219+
class EdlySiteDeletionViewSet(APIView):
220+
"""
221+
Delete the ecommerce site and its data.
222+
"""
223+
permission_classes = [IsAuthenticated, CanAccessSiteDeletion]
224+
225+
def post(self, request):
226+
"""
227+
POST /edly_ecommerce_api/delete_site/
228+
"""
229+
try:
230+
with transaction.atomic():
231+
self.process_site_deletion(request)
232+
233+
return Response(
234+
{'success': ERROR_MESSAGES.get('SITE_DELETION_SUCCESS')},
235+
status=status.HTTP_200_OK
236+
)
237+
except Exception as e:
238+
logger.error(f"Site deletion failed: {str(e)}", exc_info=True)
239+
return Response(
240+
{
241+
'error': ERROR_MESSAGES.get('SITE_DELETION_FAILURE'),
242+
'detail': str(e)
243+
},
244+
status=status.HTTP_400_BAD_REQUEST
245+
)
246+
247+
def get_current_site(self, request):
248+
"""Get current site value using domain value from request."""
249+
site_domain = request.data.get('delete_site_url', '')
250+
try:
251+
site = Site.objects.get(domain=site_domain)
252+
except Site.DoesNotExist:
253+
site = None
254+
return site
255+
256+
def get_current_partner(self, request):
257+
site = self.get_current_site(request)
258+
try:
259+
partner = Partner.objects.get(default_site=site)
260+
except Partner.DoesNotExist:
261+
partner = None
262+
return partner
263+
264+
def delete_users(self, request):
265+
"""Delete all the sync user for a given site."""
266+
user_emails = request.data.get('emails')
267+
user_names = request.data.get('usernames')
268+
if not all([len(user_emails), len(user_names)]):
269+
logger.info(f"No user deleted for given site : {(str(request.site))}")
270+
return
271+
272+
users = User.objects.filter(
273+
email__in=user_emails,
274+
username__in=user_names
275+
).exclude(id=request.user.id)
276+
277+
MarkOrdersStatusCompleteConfig.objects.filter(changed_by__in=users).delete()
278+
279+
users.delete()
280+
logger.info(f"Successfully deleted user for {request.site}")
281+
282+
def delete_courses(self, request):
283+
"""Delete the courses for the site."""
284+
partner = self.get_current_partner(request)
285+
courses = Course.objects.filter(partner=partner)
286+
287+
course_ids = courses.values_list('id', flat=True)
288+
289+
HistoricalCourse = Course.history.model
290+
HistoricalCourse.objects.filter(id__in=course_ids).delete()
291+
courses.delete()
292+
293+
def delete_partner_additional_data(self, request):
294+
"""
295+
Delete all related data referencing the partner that is not automatically
296+
deleted due to on_delete settings.
297+
"""
298+
partner = self.get_current_partner(request)
299+
300+
HistoricalConditionalOffer = ConditionalOffer.history.model
301+
count, _ = HistoricalConditionalOffer.objects.filter(partner=partner).delete()
302+
logger.info(f"Deleted {count} HistoricalConditionalOffer record(s) for partner {partner}")
303+
304+
HistoricalOrder = Order.history.model
305+
count, _ = HistoricalOrder.objects.filter(partner=partner).delete()
306+
logger.info(f"Deleted {count} HistoricalOrder record(s) for partner {partner}")
307+
308+
count, _ = Line.objects.filter(partner=partner).delete()
309+
logger.info(f"Deleted {count} Line record(s) for partner {partner}")
310+
HistoricalLine = Line.history.model
311+
count, _ = HistoricalLine.objects.filter(partner=partner).delete()
312+
logger.info(f"Deleted {count} HistoricalLine record(s) for partner {partner}")
313+
314+
HistoricalStockRecord = StockRecord.history.model
315+
count, _ = HistoricalStockRecord.objects.filter(partner=partner).delete()
316+
logger.info(f"Deleted {count} HistoricalStockRecord record(s) for partner {partner}")
317+
318+
partner.history.all().delete()
319+
320+
321+
def delete_site(self, request):
322+
"""Delete the site and partner for a given site."""
323+
site = self.get_current_site(request)
324+
site_partner = self.get_current_partner(request)
325+
site_partner.delete()
326+
site.delete()
327+
logger.info(f"Successfully deleted site {site} and its partner")
328+
329+
def process_site_deletion(self, request):
330+
"""
331+
Process deletion of a site.
332+
"""
333+
self.delete_courses(request)
334+
self.delete_partner_additional_data(request)
335+
self.delete_users(request)
336+
self.delete_site(request)
337+
338+
208339

209340
class EdlySiteConfigViewset(APIView):
210341
"""

ecommerce/extensions/edly_ecommerce_app/permissions.py

+11
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,14 @@ def has_permission(self, request, view):
2424
Checks for user's permission for current site.
2525
"""
2626
return request.user.is_staff and request.user.username == EDLY_PANEL_WORKER_USER
27+
28+
class CanAccessSiteDeletion(BasePermission):
29+
"""
30+
Checks if a user has the access to create and update methods for sites.
31+
"""
32+
33+
def has_permission(self, request, view):
34+
"""
35+
Checks for user's permission for current site.
36+
"""
37+
return request.user.username == EDLY_PANEL_WORKER_USER

0 commit comments

Comments
 (0)