Created initial publish functionality, layout polish
This commit is contained in:
parent
4f92620875
commit
81b0534ad0
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -10,6 +10,7 @@ local_settings.py
|
|||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
media
|
media
|
||||||
|
.idea
|
||||||
|
|
||||||
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
|
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
|
||||||
# in your Git repository. Update and uncomment the following line accordingly.
|
# in your Git repository. Update and uncomment the following line accordingly.
|
||||||
|
|||||||
7
backend/apimanager/custom_storage.py
Normal file
7
backend/apimanager/custom_storage.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.core.files.storage import FileSystemStorage
|
||||||
|
|
||||||
|
class CustomStorage(FileSystemStorage):
|
||||||
|
def __init__(self, location=None, base_url=None):
|
||||||
|
location = location or 'deploy/data/' # Define your custom path
|
||||||
|
base_url = base_url or ''
|
||||||
|
super().__init__(location, base_url)
|
||||||
@ -2,13 +2,15 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import random
|
import random
|
||||||
|
import json
|
||||||
|
import ast
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics, status
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.parsers import MultiPartParser, FormParser
|
from rest_framework.parsers import MultiPartParser, FormParser
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import JsonResponse
|
|
||||||
#################################################################
|
#################################################################
|
||||||
#API related imports
|
#API related imports
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -26,9 +28,7 @@ from .serializers import (
|
|||||||
)
|
)
|
||||||
################################################################
|
################################################################
|
||||||
#Custom Imports
|
#Custom Imports
|
||||||
from .MediaHandler import (
|
from .custom_storage import CustomStorage
|
||||||
MediaHandler,
|
|
||||||
)
|
|
||||||
|
|
||||||
#UserData related views#############################################
|
#UserData related views#############################################
|
||||||
class UserDataUpdateAPIView(generics.RetrieveUpdateAPIView):
|
class UserDataUpdateAPIView(generics.RetrieveUpdateAPIView):
|
||||||
@ -114,7 +114,7 @@ class BlogDeleteAPIView(generics.DestroyAPIView):
|
|||||||
|
|
||||||
def remove_directory(self, instance):
|
def remove_directory(self, instance):
|
||||||
print(f"Deleting media files for {instance}")
|
print(f"Deleting media files for {instance}")
|
||||||
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', 'blog', str(instance.blog_id))
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'data', 'blog', str(instance.blog_id))
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(media_folder)
|
shutil.rmtree(media_folder)
|
||||||
print(f"Directory '{media_folder}' and all its contents have been removed")
|
print(f"Directory '{media_folder}' and all its contents have been removed")
|
||||||
@ -133,7 +133,7 @@ class MediaUpload(APIView):
|
|||||||
files = request.FILES.getlist('media')
|
files = request.FILES.getlist('media')
|
||||||
resource_type = file_serializer.validated_data['resource_type']
|
resource_type = file_serializer.validated_data['resource_type']
|
||||||
resource_id = file_serializer.validated_data['resource_id']
|
resource_id = file_serializer.validated_data['resource_id']
|
||||||
file_path_base = f'rangolio_data'
|
file_path_base = f'data'
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
file_unique_slug = ''.join(random.choices('ABCDEabcde12345', k=6))
|
file_unique_slug = ''.join(random.choices('ABCDEabcde12345', k=6))
|
||||||
@ -150,26 +150,26 @@ class MediaUpload(APIView):
|
|||||||
class ListMedia(APIView):
|
class ListMedia(APIView):
|
||||||
def get(self, request, resource_type, resource_id, format=None):
|
def get(self, request, resource_type, resource_id, format=None):
|
||||||
if resource_id != resource_type:
|
if resource_id != resource_type:
|
||||||
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, resource_id, 'media')
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'data', resource_type, resource_id, 'media')
|
||||||
else:
|
else:
|
||||||
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, 'media')
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'data', resource_type, 'media')
|
||||||
|
|
||||||
if not os.path.exists(media_folder):
|
if not os.path.exists(media_folder):
|
||||||
return Response({'error': 'Media directory not found'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'error': 'Media directory not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
media_files = [f for f in os.listdir(media_folder) if f.endswith(('.png', '.jpg', '.jpeg'))]
|
media_files = [f for f in os.listdir(media_folder) if f.endswith(('.png', '.jpg', '.jpeg'))]
|
||||||
if resource_id != resource_type:
|
if resource_id != resource_type:
|
||||||
media_urls = [request.build_absolute_uri(f'{settings.MEDIA_URL}rangolio_data/{resource_type}/{resource_id}/media/' + f) for f in media_files]
|
media_urls = [request.build_absolute_uri(f'{settings.MEDIA_URL}data/{resource_type}/{resource_id}/media/' + f) for f in media_files]
|
||||||
else:
|
else:
|
||||||
media_urls = [request.build_absolute_uri(f'{settings.MEDIA_URL}rangolio_data/{resource_type}/media/' + f) for f in media_files]
|
media_urls = [request.build_absolute_uri(f'{settings.MEDIA_URL}data/{resource_type}/media/' + f) for f in media_files]
|
||||||
|
|
||||||
return Response({'media': media_urls}, status=status.HTTP_200_OK)
|
return Response({'media': media_urls}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def delete(self, request, resource_type, resource_id, format=None):
|
def delete(self, request, resource_type, resource_id, format=None):
|
||||||
if resource_id != resource_type:
|
if resource_id != resource_type:
|
||||||
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, resource_id, 'media')
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'data', resource_type, resource_id, 'media')
|
||||||
else:
|
else:
|
||||||
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, 'media')
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'data', resource_type, 'media')
|
||||||
file_name = request.query_params.get('file')
|
file_name = request.query_params.get('file')
|
||||||
if not file_name or not file_name.endswith(('.png', '.jpg', '.jpeg')):
|
if not file_name or not file_name.endswith(('.png', '.jpg', '.jpeg')):
|
||||||
return Response({'error': 'Invalid or no file name provided'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'error': 'Invalid or no file name provided'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@ -183,10 +183,107 @@ class ListMedia(APIView):
|
|||||||
|
|
||||||
class Publish(APIView):
|
class Publish(APIView):
|
||||||
def get(self, request, deploy_type, format=None):
|
def get(self, request, deploy_type, format=None):
|
||||||
self.create_json()
|
storage = CustomStorage()
|
||||||
|
self.create_json(storage)
|
||||||
return Response({"deploy_type": deploy_type}, status=status.HTTP_200_OK)
|
return Response({"deploy_type": deploy_type}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create_json(self):
|
def create_json(self, storage):
|
||||||
print(UserData.objects.all())
|
self.create_user_data_json(UserData.objects.first(), storage)
|
||||||
print(Category.objects.all())
|
self.create_theme_data_json(UserData.objects.first(), storage)
|
||||||
print(Blog.objects.all())
|
self.create_category_data_json(Category, storage)
|
||||||
|
self.create_blog_data_json(Blog.objects.all(), storage)
|
||||||
|
self.merge_media()
|
||||||
|
|
||||||
|
def create_user_data_json(self, instance, storage):
|
||||||
|
json_content = {
|
||||||
|
"name": instance.name,
|
||||||
|
"introContent": instance.intro_content,
|
||||||
|
"profilePhoto": self.sanitize_media_link(instance.profile_photo),
|
||||||
|
"builtWith": instance.built_with
|
||||||
|
}
|
||||||
|
self.save_json(json_content, 'shared/user-data.json', storage)
|
||||||
|
|
||||||
|
def create_theme_data_json(self, instance, storage):
|
||||||
|
json_content = {
|
||||||
|
"defaultTheme": instance.default_theme,
|
||||||
|
"darkTheme": ast.literal_eval(instance.dark_theme),
|
||||||
|
"lightTheme": ast.literal_eval(instance.light_theme),
|
||||||
|
}
|
||||||
|
self.save_json(json_content, 'shared/theme-config.json', storage)
|
||||||
|
|
||||||
|
def create_category_data_json(self, model_instance, storage):
|
||||||
|
categories = []
|
||||||
|
instance_objects = model_instance.objects.all()
|
||||||
|
if not instance_objects.exists():
|
||||||
|
self.save_json([], 'category/category-metadata.json', storage)
|
||||||
|
else:
|
||||||
|
for eachInstance in instance_objects:
|
||||||
|
instance_data = {
|
||||||
|
"id": str(eachInstance.category_id),
|
||||||
|
"name": eachInstance.name,
|
||||||
|
"coverImage": self.sanitize_media_link(eachInstance.cover_image),
|
||||||
|
"tagLine": eachInstance.tagline,
|
||||||
|
"description": eachInstance.description,
|
||||||
|
"featuredBlog": eachInstance.featured_id
|
||||||
|
}
|
||||||
|
categories.append(instance_data)
|
||||||
|
self.create_instance_data(instance_data, model_instance.objects.get(category_id=eachInstance.category_id), storage)
|
||||||
|
self.save_json(categories, 'category/category-metadata.json', storage)
|
||||||
|
|
||||||
|
|
||||||
|
def create_blog_data_json(self, instance, storage):
|
||||||
|
print(instance)
|
||||||
|
if not instance.exists():
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for eachBlog in instance:
|
||||||
|
instance_data = {
|
||||||
|
"id": str(eachBlog.blog_id),
|
||||||
|
"name": eachBlog.name,
|
||||||
|
"description": eachBlog.description,
|
||||||
|
"coverimage": self.sanitize_media_link(eachBlog.cover_image),
|
||||||
|
"tagLine": eachBlog.tagline,
|
||||||
|
"parentCategory": str(eachBlog.parent_category.category_id),
|
||||||
|
"contentBody": eachBlog.content_body
|
||||||
|
}
|
||||||
|
self.save_json(instance_data, f'blog/{instance_data["id"]}/blog-data.json', storage)
|
||||||
|
|
||||||
|
def merge_media(self):
|
||||||
|
source_dir = 'media/data'
|
||||||
|
destination_dir = 'deploy/data'
|
||||||
|
if not os.path.exists(source_dir):
|
||||||
|
print("The source directory does not exist.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
shutil.copytree(source_dir, destination_dir, dirs_exist_ok=True)
|
||||||
|
print(f"Directory copied successfully from {source_dir} to {destination_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error occurred: {e}")
|
||||||
|
|
||||||
|
def create_instance_data(self, instance_data, blogs_by_category_instance, storage):
|
||||||
|
instance_data["blogMetadata"]=[]
|
||||||
|
blogs = blogs_by_category_instance.blogs.all()
|
||||||
|
if not blogs.exists():
|
||||||
|
self.save_json(instance_data, f'category/{instance_data["id"]}/category-data.json', storage)
|
||||||
|
else:
|
||||||
|
for eachBlog in blogs:
|
||||||
|
instance_data["blogMetadata"].append({
|
||||||
|
"id": str(eachBlog.blog_id),
|
||||||
|
"name": eachBlog.name,
|
||||||
|
"description": eachBlog.description,
|
||||||
|
"coverimage": self.sanitize_media_link(eachBlog.cover_image),
|
||||||
|
"tagLine": eachBlog.tagline,
|
||||||
|
"parentCategory": instance_data["id"]
|
||||||
|
})
|
||||||
|
self.save_json(instance_data, f'category/{instance_data["id"]}/category-data.json', storage)
|
||||||
|
|
||||||
|
def save_json(self, json_content, file_name, storage):
|
||||||
|
data_json = json.dumps(json_content, indent=2)
|
||||||
|
if storage.exists(file_name):
|
||||||
|
storage.delete(file_name)
|
||||||
|
storage.save(file_name, ContentFile(data_json.encode('utf-8')))
|
||||||
|
def sanitize_media_link(self, string):
|
||||||
|
if not string:
|
||||||
|
return ''
|
||||||
|
return string.replace('http://127.0.0.1:8000/media/data/', '')
|
||||||
|
|
||||||
|
|||||||
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -9,6 +9,7 @@ lerna-debug.log*
|
|||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
public/data
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
|||||||
@ -166,10 +166,7 @@ function Blog(props) {
|
|||||||
<Col xs="3" className="d-none d-md-block"></Col>
|
<Col xs="3" className="d-none d-md-block"></Col>
|
||||||
<Col className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`} style={{marginBottom: '25px'}}>
|
<Col className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`} style={{marginBottom: '25px'}}>
|
||||||
<EditorComponent notificationToggler={props.notificationToggler} setContent={setBlogContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={blogContent} resourceType='blog' resourceId={blogData.id}/>
|
<EditorComponent notificationToggler={props.notificationToggler} setContent={setBlogContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={blogContent} resourceType='blog' resourceId={blogData.id}/>
|
||||||
<ButtonGroup className='mt-4'>
|
<Button className='mt-3 mb-2' onClick={(event) => setInfo(event)} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||||
<Button onClick={(event) => setInfo(event)} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
|
||||||
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="3" className="d-none d-md-block"></Col>
|
<Col xs="3" className="d-none d-md-block"></Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@ -140,10 +140,7 @@ function Blogs(props) {
|
|||||||
itemObject={item}
|
itemObject={item}
|
||||||
/>
|
/>
|
||||||
)) : <Spinner />}
|
)) : <Spinner />}
|
||||||
<ButtonGroup className='mt-4'>
|
<Button className='mt-3 mb-2' onClick={() => updateInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||||
<Button onClick={() => updateInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
|
||||||
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import EditableMediaService from '../../services/editable-media-service'
|
|||||||
function HomePage(props) {
|
function HomePage(props) {
|
||||||
const [introContent, setIntroContent] = useState("")
|
const [introContent, setIntroContent] = useState("")
|
||||||
const [profilePhoto, setProfilePhoto] = useState(false)
|
const [profilePhoto, setProfilePhoto] = useState(false)
|
||||||
const [saveKeyReady, setSaveKeyReady] = useState(true)
|
|
||||||
const [nameFieldInvalid, setNameFieldInvalid] = useState(false)
|
const [nameFieldInvalid, setNameFieldInvalid] = useState(false)
|
||||||
const [modal, setModal] = useState(false)
|
const [modal, setModal] = useState(false)
|
||||||
const nameField = useRef(null)
|
const nameField = useRef(null)
|
||||||
@ -61,7 +60,7 @@ function HomePage(props) {
|
|||||||
<Row className='d-md-block'>
|
<Row className='d-md-block'>
|
||||||
<Col xs="4" className="d-none d-md-block"></Col>
|
<Col xs="4" className="d-none d-md-block"></Col>
|
||||||
{UserData.profilePhoto !== "" && (
|
{UserData.profilePhoto !== "" && (
|
||||||
<center><img style={{ width: '180px', height: '180px', objectFit: 'cover' }} className="rounded-circle" src={EditableMediaService.getMedia(UserData.profilePhoto)} alt="Profile Photo" /></center>
|
<center><img style={{ width: '180px', height: '180px', objectFit: 'cover' }} className="mt-5 mb-2 rounded-circle" src={EditableMediaService.getMedia(UserData.profilePhoto)} alt="Profile Photo" /></center>
|
||||||
)}
|
)}
|
||||||
<Col xs="4" className="d-none d-md-block"></Col>
|
<Col xs="4" className="d-none d-md-block"></Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -97,10 +96,7 @@ function HomePage(props) {
|
|||||||
</FormFeedback>}
|
</FormFeedback>}
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<EditorComponent notificationToggler={props.notificationToggler} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={UserData.introContent} setContent={setIntroContent} resourceType='homepage' resourceId='homepage' />
|
<EditorComponent notificationToggler={props.notificationToggler} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={UserData.introContent} setContent={setIntroContent} resourceType='homepage' resourceId='homepage' />
|
||||||
<ButtonGroup className={`mt-4`}>
|
<Button className='mt-3 mb-2' onClick={() => setInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||||
<Button onClick={() => setInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
|
||||||
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="3" className="d-none d-md-block"></Col>
|
<Col xs="3" className="d-none d-md-block"></Col>
|
||||||
|
|
||||||
|
|||||||
@ -152,6 +152,7 @@ function Header(props) {
|
|||||||
outline
|
outline
|
||||||
onClick={() => setInfo(null, null, 'darkTheme')}>Dark Theme</Button>
|
onClick={() => setInfo(null, null, 'darkTheme')}>Dark Theme</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`} className='ms-5' outline>Publish Data</Button>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -65,7 +65,7 @@ function Blog(props) {
|
|||||||
<Row className="mb-4">
|
<Row className="mb-4">
|
||||||
<Col className="p-0">
|
<Col className="p-0">
|
||||||
{
|
{
|
||||||
blogData.coverImage !== "" ?
|
blogData.coverImage ?
|
||||||
<img
|
<img
|
||||||
src={MediaService.getMedia(blogData.coverImage)}
|
src={MediaService.getMedia(blogData.coverImage)}
|
||||||
alt="Banner"
|
alt="Banner"
|
||||||
|
|||||||
@ -19,7 +19,7 @@ function CardListViewer(props) {
|
|||||||
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0)
|
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0)
|
||||||
return (
|
return (
|
||||||
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
|
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
|
||||||
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
|
{itemObject.coverImage ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Link to={`/${props.resourceType}/${itemObject.id}`}>
|
<Link to={`/${props.resourceType}/${itemObject.id}`}>
|
||||||
<CardTitle className={`${props.textColor}`} tag="h5">
|
<CardTitle className={`${props.textColor}`} tag="h5">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user