diff --git a/resume_manager_core/fixtures/resume_manager/resume_sections.json b/resume_manager_core/fixtures/resume_manager/resume_sections.json index 24d854343d5e80b3c89524a8df9145dea5b968a3..f1357358a1fbcdac2d97a033aefec0fb641c5725 100644 --- a/resume_manager_core/fixtures/resume_manager/resume_sections.json +++ b/resume_manager_core/fixtures/resume_manager/resume_sections.json @@ -134,7 +134,7 @@ "pk": 14, "fields": { "name": "Text: Left-Align", - "css_classes": "resume-section-text left-align", + "css_classes": "resume-section-text align-left", "date_created": "2023-01-01T08:00:00.000Z", "date_modified": "2023-01-01T08:00:00.000Z" } @@ -144,7 +144,7 @@ "pk": 15, "fields": { "name": "Text: Center-Align", - "css_classes": "resume-section-text center-align", + "css_classes": "resume-section-text align-center", "date_created": "2023-01-01T08:00:00.000Z", "date_modified": "2023-01-01T08:00:00.000Z" } @@ -154,7 +154,7 @@ "pk": 16, "fields": { "name": "Text: Right-Align", - "css_classes": "resume-section-text right-align", + "css_classes": "resume-section-text align-right", "date_created": "2023-01-01T08:00:00.000Z", "date_modified": "2023-01-01T08:00:00.000Z" } @@ -168,5 +168,25 @@ "date_created": "2023-01-01T08:00:00.000Z", "date_modified": "2023-01-01T08:00:00.000Z" } +}, +{ + "model": "resume_manager_core.resumesection", + "pk": 18, + "fields": { + "name": "Row: Minimal", + "css_classes": "resume-section-row minimal", + "date_created": "2023-01-01T08:00:00.000Z", + "date_modified": "2023-01-01T08:00:00.000Z" + } +}, +{ + "model": "resume_manager_core.resumesection", + "pk": 19, + "fields": { + "name": "Column: Minimal", + "css_classes": "resume-section-column minimal", + "date_created": "2023-01-01T08:00:00.000Z", + "date_modified": "2023-01-01T08:00:00.000Z" + } } ] diff --git a/resume_manager_core/management/commands/resume_manager_core__loadfixtures.py b/resume_manager_core/management/commands/resume_manager_core__loadfixtures.py index 089e4e707363ad6ae82724b11f866d4556ba9535..89b4d3c4b1102d57a924142b3947471fae2dd474 100644 --- a/resume_manager_core/management/commands/resume_manager_core__loadfixtures.py +++ b/resume_manager_core/management/commands/resume_manager_core__loadfixtures.py @@ -15,6 +15,7 @@ from apps.Resume_Manager.resume_manager_core.models import ( ResumeToSectionIntermediary, DataSectionHeader, DataItem, + DataKeyValue, ) from workspace_core.settings.reusable_settings import ConsoleColors @@ -87,30 +88,34 @@ class Command(BaseCommand): ) # Get resume section models. - section_format_page = ResumeSection.objects.get(name='Page') - section_format_row_full = ResumeSection.objects.get(name='Row: Full') - section_format_column_full = ResumeSection.objects.get(name='Column: Full') - section_format_text_left = ResumeSection.objects.get(name='Text: Left-Align') - section_format_text_center = ResumeSection.objects.get(name='Text: Center-Align') - section_format_text_right = ResumeSection.objects.get(name='Text: Right-Align') - section_format_style_full = ResumeSection.objects.get(name='Style: Full Color') + section_format__page = ResumeSection.objects.get(name='Page') + section_format__row__minimal = ResumeSection.objects.get(name='Row: Minimal') + section_format__row__full = ResumeSection.objects.get(name='Row: Full') + section_format__row__one_fifth = ResumeSection.objects.get(name='Row: One-Fifth') + section_format__row__three_fifths = ResumeSection.objects.get(name='Row: Three-Fifths') + section_format__column__minimal = ResumeSection.objects.get(name='Column: Minimal') + section_format__column__full = ResumeSection.objects.get(name='Column: Full') + section_format__text__left = ResumeSection.objects.get(name='Text: Left-Align') + section_format__text__center = ResumeSection.objects.get(name='Text: Center-Align') + section_format__text__right = ResumeSection.objects.get(name='Text: Right-Align') + section_format__style__full_color = ResumeSection.objects.get(name='Style: Full Color') # For each resume, create 4 pages to showcase different possible formats. page_1 = ResumeToSectionIntermediary.objects.create( resume=current_resume, - section_format=section_format_page, + section_format=section_format__page, ) page_2 = ResumeToSectionIntermediary.objects.create( resume=current_resume, - section_format=section_format_page, + section_format=section_format__page, ) page_3 = ResumeToSectionIntermediary.objects.create( resume=current_resume, - section_format=section_format_page, + section_format=section_format__page, ) page_4 = ResumeToSectionIntermediary.objects.create( resume=current_resume, - section_format=section_format_page, + section_format=section_format__page, ) # Create page 1 main subsections. @@ -118,48 +123,130 @@ class Command(BaseCommand): page_1_header = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1, - section_format=section_format_row_full, + section_format=section_format__row__one_fifth, ) page_1_header_background = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1_header, - section_format=section_format_style_full, + section_format=section_format__style__full_color, ) page_1_header_left = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1_header_background, - section_format=section_format_row_full, + section_format=section_format__row__full, ) page_1_header_name = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1_header_left, - section_format=section_format_text_left, + section_format=section_format__text__left, + title='Example User', ) page_1_header_right = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1_header_background, - section_format=section_format_column_full, + section_format=section_format__column__full, ) page_1_header_profession = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1_header_right, - section_format=section_format_text_center, + section_format=section_format__text__center + ) + ResumeToSectionIntermediary.objects.create( + resume=current_resume, + parent_section=page_1_header_profession, + section_format=section_format__text__right, + title='Software', + ) + ResumeToSectionIntermediary.objects.create( + resume=current_resume, + parent_section=page_1_header_profession, + section_format=section_format__text__right, + title='Engineer', ) page_1_middle = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1, - section_format=section_format_column_full, + section_format=section_format__column__full, + ) + page_1_middle_contact = ResumeToSectionIntermediary.objects.create( + resume=current_resume, + parent_section=page_1_middle, + section_format=section_format__column__full, + title='Contact Info', + ) + page_1_middle_experience = ResumeToSectionIntermediary.objects.create( + resume=current_resume, + parent_section=page_1_middle, + section_format=section_format__column__full, + title='Experience', + ) + page_1_middle_education = ResumeToSectionIntermediary.objects.create( + resume=current_resume, + parent_section=page_1_middle, + section_format=section_format__column__full, + title='Education', ) page_1_footer = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1, - section_format=section_format_row_full, + section_format=section_format__row__one_fifth, ) page_1_footer_background = ResumeToSectionIntermediary.objects.create( resume=current_resume, parent_section=page_1_footer, - section_format=section_format_style_full, + section_format=section_format__style__full_color, + ) + + # Create data for page 1. + DataKeyValue.objects.create( + resume_section=page_1_middle_contact, + data_key='Phone', + data_value='(123) 456-7890', + ) + DataKeyValue.objects.create( + resume_section=page_1_middle_contact, + data_key='Email', + data_value=example_user.email, + ) + DataKeyValue.objects.create( + resume_section=page_1_middle_contact, + data_key='GitHub', + data_value='idk.github.com/example_user', + ) + DataItem.objects.create( + resume_section=page_1_middle_experience, + title_left='Generic Company - Software Engineer', + title_right='2020 - Present', + item='Nulla ut mollis ipsum. Fusce ultricies a lectus ac condimentum. Aliquam vitae iaculis nibh. Sed nec tortor ac arcu tincidunt vestibulum sit amet in neque.', + show_bullet_point=False, + ) + DataItem.objects.create( + resume_section=page_1_middle_experience, + item='Donec varius velit sed ultricies pellentesque. Ut pulvinar ut justo vel scelerisque. Vestibulum convallis nunc tellus, vitae elementum nibh interdum sed. Donec eget magna porta nisl pulvinar gravida id non risus.', + show_bullet_point=False, + ) + # DataItem.objects.create( + # resume_section=page_1_middle_experience, + # item='Nullam mollis odio tortor, sed interdum quam porttitor eu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam at nulla vulputate, imperdiet velit id, molestie sem. Aliquam tellus urna, laoreet id risus sed, ullamcorper ornare massa. Maecenas quis metus a eros rutrum lacinia. Nullam fermentum sit amet leo at viverra. Ut lobortis leo feugiat congue finibus. Ut vitae volutpat purus, sit amet maximus magna. Vestibulum bibendum, ex quis aliquet ultricies, quam dui ultrices arcu, eget laoreet nunc metus eget sapien. Maecenas dictum leo vitae risus vestibulum, ac posuere diam molestie. In hac habitasse platea dictumst. Etiam sapien ex, ullamcorper a hendrerit ac, luctus vitae metus. Mauris euismod leo vel diam efficitur varius.', + # show_bullet_point=False, + # ) + DataItem.objects.create( + resume_section=page_1_middle_experience, + title_left='Generic Company - IT Support', + title_right='2015 - 2020', + item='Nunc eu purus id sapien pretium fringilla. Etiam eu mi lobortis, sollicitudin metus finibus, pellentesque nisi. Ut tempus convallis justo, ac euismod dui consequat vitae. Integer iaculis purus nec lorem facilisis aliquam.', + show_bullet_point=False, + ) + DataItem.objects.create( + resume_section=page_1_middle_experience, + item='Vestibulum sagittis dolor vitae ex interdum, at scelerisque orci sollicitudin. Proin urna libero, eleifend at ex vel, tempus auctor tellus. Phasellus posuere sit amet tortor et interdum. Morbi viverra vehicula sem, a lobortis neque tincidunt ut. Donec viverra vulputate metus. Sed quis tortor eget lorem tempor iaculis a eu mauris.', + show_bullet_point=False, ) + # DataItem.objects.create( + # resume_section=page_1_middle_experience, + # item='Proin semper est sapien, eu pellentesque libero efficitur eget. Aliquam vel commodo felis. In id tortor libero. Nam diam ante, pulvinar eget orci sed, sagittis scelerisque odio. Praesent tristique sagittis enim, ac malesuada risus lobortis ut. Proin sit amet lacus sed diam commodo hendrerit convallis at lectus. Donec vestibulum orci enim, ut dignissim velit vehicula eget. Vivamus nunc dui, rutrum sit amet nunc vitae, placerat maximus eros. Nam elit augue, accumsan sit amet porta ac, congue vitae arcu. Mauris imperdiet placerat ante pharetra iaculis. Cras ut arcu pellentesque, gravida ipsum quis, porta velit.', + # show_bullet_point=False, + # ) # Create page 2 main subsections. # Mimics a full resume in single-page format. diff --git a/resume_manager_core/models.py b/resume_manager_core/models.py index 99c25fff05299e4f32b64624d6ffa69c80980f20..922945d69d57d4f47655e15e53d83adc00c63bc6 100644 --- a/resume_manager_core/models.py +++ b/resume_manager_core/models.py @@ -98,6 +98,7 @@ class ResumeToSectionIntermediary(WorkspaceModel): resume = models.ForeignKey('Resume', on_delete=models.CASCADE) parent_section = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True) section_format = models.ForeignKey('ResumeSection', on_delete=models.CASCADE) + title = models.CharField(max_length=settings.MAX_LENGTH, blank=True, null=True) is_data_subsection = models.BooleanField(default=False) # display_category = models.PositiveSmallIntegerField(choices=DISPLAY_CATEGORY_CHOICES, default=0) @@ -116,8 +117,6 @@ class ResumeToSectionIntermediary(WorkspaceModel): return '{0} - {1}'.format(self.resume, self.section_format) -# region Resume Data - class DataSectionHeader(WorkspaceModel): """Implementation of a header for a data section. @@ -128,19 +127,18 @@ class DataSectionHeader(WorkspaceModel): resume_section = models.ForeignKey('ResumeToSectionIntermediary', on_delete=models.CASCADE) # Model fields. - section_name = models.CharField(max_length=settings.MAX_LENGTH) - header = models.TextField(blank=True) + header = models.CharField(max_length=settings.MAX_LENGTH) class Meta: verbose_name = 'Data Section Header' verbose_name_plural = 'Data Section Headers' def __str__(self): - return '{0} - {1}'.format(self.resume_section.resume, self.section_name) + return '{0} - {1}'.format(self.resume_section.resume, self.header[15:]) class DataItem(WorkspaceModel): - """Abstract implementation of a data item. + """Implementation of a data item. If a data section has only a single item, then the item is displayed in block/paragraph format. If a data section has two or more items, then they are displayed in bullet point format. @@ -150,8 +148,10 @@ class DataItem(WorkspaceModel): resume_section = models.ForeignKey('ResumeToSectionIntermediary', on_delete=models.CASCADE) # Model fields. - section_name = models.CharField(max_length=settings.MAX_LENGTH) - item = models.TextField(blank=True) + title_left = models.CharField(max_length=settings.MAX_LENGTH, blank=True, null=True) + title_right = models.CharField(max_length=settings.MAX_LENGTH, blank=True, null=True) + item = models.TextField(max_length=500) + show_bullet_point = models.BooleanField(default=True) is_emphasized = models.BooleanField(default=False) is_minimized = models.BooleanField(default=False) @@ -160,7 +160,7 @@ class DataItem(WorkspaceModel): verbose_name_plural = 'Data Section Items' def __str__(self): - return '{0} - {1}'.format(self.resume_section.resume, self.section_name) + return '{0} - {1}'.format(self.resume_section.resume, self.item[15:]) def clean(self): """ @@ -172,4 +172,20 @@ class DataItem(WorkspaceModel): if self.is_emphasized and self.is_minimized: raise ValidationError('Data cannot be both emphasized and minimized in style.') -# endregion Resume Data + +class DataKeyValue(WorkspaceModel): + """Implementation of a data key-value pair.""" + + # Relationship Keys. + resume_section = models.ForeignKey('ResumeToSectionIntermediary', on_delete=models.CASCADE) + + # Model fields. + data_key = models.CharField(max_length=settings.MAX_LENGTH) + data_value = models.CharField(max_length=settings.MAX_LENGTH) + + class Meta: + verbose_name = 'Data Section Key-Value Pair' + verbose_name_plural = 'Data Section Key-Value Pairs' + + def __str__(self): + return '{0} - {1}'.format(self.resume_section.resume, self.data_key) diff --git a/resume_manager_core/static/resume_manager_core/css/sass/base.scss b/resume_manager_core/static/resume_manager_core/css/sass/base.scss new file mode 100644 index 0000000000000000000000000000000000000000..ef1dddbe459d23a549d0049782fae90044c43c89 --- /dev/null +++ b/resume_manager_core/static/resume_manager_core/css/sass/base.scss @@ -0,0 +1,13 @@ + +//============================// +//==== Resume-Manager CSS ====// +//============================// + +// CSS for Resume-Manager project app. + + +//================================================================================// +//================================ Core CSS Logic ================================// +//================================================================================// + +@use './src/_resume_blocky'; diff --git a/resume_manager_core/static/resume_manager_core/css/sass/src/_resume_blocky.scss b/resume_manager_core/static/resume_manager_core/css/sass/src/_resume_blocky.scss new file mode 100644 index 0000000000000000000000000000000000000000..45a7fa96c3dbf1cbd088bf55498fcb2640f3ff86 --- /dev/null +++ b/resume_manager_core/static/resume_manager_core/css/sass/src/_resume_blocky.scss @@ -0,0 +1,165 @@ + +//=============================// +//==== Dev-Tools Color CSS ====// +//=============================// + +// CSS for DevTools "color test" views. + + +//================================================================================// +//==================================== Imports ===================================// +//================================================================================// + +// Import variables. +@use '../../../../../../../../core/static/core/css/sass/src/variables/init' as var; + + +//================================================================================// +//==================================== Styles ====================================// +//================================================================================// + +//===========================================// +//==== START Resume Theme "Blocky" Block ====// +//===========================================// + +.color_section { + @include var.mixin-display-flex; + @include var.mixin-flex-direction(column); + @include var.mixin-justify-content(space-evenly); + @include var.mixin-align-items(center); + + width: 100%; + padding-top: 5px; + padding-bottom: 5px; +} + + +html, body { + margin: 0; + padding: 0; +} + + +html { + + .resume-page.full-size { + width: 8.5in; + height: 11in; + margin: 1.5cm; + } + + // General Styles. + .resume-page { + + position: relative; + + @include var.mixin-display-flex; + @include var.mixin-flex-direction(column); + @include var.mixin-flex-wrap(wrap); + @include var.mixin-justify-content(space-evenly); + @include var.mixin-align-items(center); + @include var.mixin-flex(1); + + width: 100%; +// height: 100%; + margin: 0; + padding: 0; + + border: 1px solid black; + + box-sizing: border-box; + + div, ul, li { + position: relative; + + @include var.mixin-display-flex; + @include var.mixin-flex-wrap(wrap); + @include var.mixin-justify-content(space-evenly); + @include var.mixin-align-items(center); + @include var.mixin-flex(1); + + width: 100%; + height: 100%; + margin: 0; + padding: 0; + + box-sizing: border-box; + } + + p { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + } + + h2, h3, h4 { + width: 100%; +// height: 100%; + margin: 0; + padding: 0; + } + + .resume-section-row, + .resume-section-row > ul, + .resume-section-row > ul > li { + @include var.mixin-flex-direction(row); + } + + .resume-section-row.minimal { + @include var.mixin-flex(0); + height: auto; + } + + .resume-section-column, + .resume-section-column > ul, + .resume-section-column > ul > li{ + @include var.mixin-flex-direction(column); + } + + .resume-section-column.minimal { + @include var.mixin-flex(0); + + width: auto; + } + + li.no-bullet { + list-style-type: none; + } + + span.bold { + font-weight: bold; + } + + .resume-section-style.full-color { + border: 1px solid black; + background-color: grey; + } + + .align-left { + text-align: left; + } + + .align-center { + text-align: center; + } + + .align-right { + text-align: right; + } + } + + // Purple blocky theme styles. + .theme-blocky.purple .resume-page .resume-section-style.full-color { + border-color: #27212e; + background-color: #27212e; + + h2, h3, h4 { + color: #fff; + } + + span.bold { + color: #233d74; + } + } +} diff --git a/resume_manager_core/templates/resume_manager/_display_recurse.html b/resume_manager_core/templates/resume_manager/_display_recurse.html new file mode 100644 index 0000000000000000000000000000000000000000..5af7c35fc89ce381603f864c67570761f904a23a --- /dev/null +++ b/resume_manager_core/templates/resume_manager/_display_recurse.html @@ -0,0 +1,49 @@ +{% load static %} + + +{% for data in data_subsection %} + <div class="{{ data.section.section_format.css_classes }}"> + {% if data.section.title %} + <h3>{{ data.section.title }}</h3> + {% endif %} + + {% if data.section_header %} + <p>{{ data.section_header }}</p> + {% endif %} + + {% if data.section_key_value_pairs %} + <ul> + {% for key_value in data.section_key_value_pairs %} + <li> + <p> + <span class="bold">{{ key_value.data_key }}</span>: + {{ key_value.data_value }} + </p> + </li> + {% endfor %} + </ul> + {% endif %} + + {% if data.section_items %} + <ul> + {% for data_item in data.section_items %} + {% if data_item.title_left or data_item.title_right %} + <h4> + {% if data_item.title_left %} + <span class="align-left">{{ data_item.title_left }}</span> + {% endif %} + {% if data_item.title_right %} + <span class="align-right">{{ data_item.title_right }}</span> + {% endif %} + </h4> + {% endif %} + <li {% if not show_bullet_point %}class="no-bullet"{% endif %}> + <p>{{ data_item.item }}</p> + </li> + {% endfor %} + </ul> + {% endif %} + + {% include 'resume_manager/_display_recurse.html' with data_subsection=data.section_children %} + </div> +{% endfor %} diff --git a/resume_manager_core/templates/resume_manager/_display_recurse_debug.html b/resume_manager_core/templates/resume_manager/_display_recurse_debug.html new file mode 100644 index 0000000000000000000000000000000000000000..76f1d94b392e5da9dd380e6802551d59abe7e579 --- /dev/null +++ b/resume_manager_core/templates/resume_manager/_display_recurse_debug.html @@ -0,0 +1,12 @@ +{% load static %} + + +<ul> + {% for data in data_subsection %} + <li> + <p>{{ data.section }}</p> + {% include 'resume_manager/_display_recurse_debug.html' with data_subsection=data.section_children %} + </li> + + {% endfor %} +</ul> diff --git a/resume_manager_core/templates/resume_manager/display.html b/resume_manager_core/templates/resume_manager/display.html new file mode 100644 index 0000000000000000000000000000000000000000..0dbd5bd5a7acfc24229c4f89b6d03acb13519b56 --- /dev/null +++ b/resume_manager_core/templates/resume_manager/display.html @@ -0,0 +1,57 @@ +<!doctype html> +{% load static %} + + +<html lang="en"> +{% block head %} + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + + {% block title %} + <title> + {% block title_page_name %}{{ resume.name }}| {% endblock title_page_name %} + {% block title_app_name %}}Resumes | {% endblock title_app_name %} + {% block title_site_name %}Devsuite Workspace{% endblock title_site_name %} + </title> + {% endblock title %} + + {% block fonts_base %} + <link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" type="text/css"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css"> + {% endblock fonts_base %} + {% block fonts_extra %}{% endblock fonts_extra %} + + {% block base_styles %} + <link rel="stylesheet" type="text/css" href="{% static 'resume_manager_core/css/base.css' %}"> + {% endblock base_styles %} + {% block extra_styles %}{% endblock extra_styles %} + </head> +{% endblock head %} + + +{% block body %} + <body class="{{ resume_theme.css_classes }}"> + + {% include 'resume_manager/_display_recurse.html' with data_subsection=resume_data %} + + <hr> + + <p>Resume:</p> + <p>{{ resume }}</p> + + <hr> + + <p>Resume Theme:</p> + <p>{{ resume_theme }}</p> + + <hr> + + <p>Resume Data:</p> + {% include 'resume_manager/_display_recurse_debug.html' with data_subsection=resume_data %} + + <hr> + + </body> +{% endblock body %} +</html> diff --git a/resume_manager_core/templates/resume_manager/index.html b/resume_manager_core/templates/resume_manager/index.html index 99b5995c831eef5565fb683bf7031aee97e6bfe5..9d4fe1456380d2893a69d85df6f570abe0c72d5c 100644 --- a/resume_manager_core/templates/resume_manager/index.html +++ b/resume_manager_core/templates/resume_manager/index.html @@ -8,7 +8,13 @@ {% if resumes %} <ul> {% for resume in resumes %} - <li><p>{{ resume.last_activity }} - {{ resume.name }}</p></li> + <li> + <p> + <a href="{% url 'resume_manager_core:display-resume' resume.pk %}"> + {{ resume.last_activity }} - {{ resume.name }} + </a> + </p> + </li> {% endfor %} </ul> {% else %} diff --git a/resume_manager_core/urls.py b/resume_manager_core/urls.py index 8516ccb87ecc31a51feb521a5e74a29e819249a8..1f752392b5489f9c33c0f5965d602fa4ef585c54 100644 --- a/resume_manager_core/urls.py +++ b/resume_manager_core/urls.py @@ -11,5 +11,6 @@ from . import views app_name = 'resume_manager_core' urlpatterns = [ + path('display/<int:pk>/', views.DisplayResume.as_view(), name='display-resume'), path('index/', views.Index.as_view(), name='index'), ] diff --git a/resume_manager_core/views.py b/resume_manager_core/views.py index a4e9cae1dc472ce2f176d8a87a4fa6b39f60a7d2..6eeeb3d3f3a4482a05d93081c66970f912d65af6 100644 --- a/resume_manager_core/views.py +++ b/resume_manager_core/views.py @@ -4,13 +4,22 @@ Views for ResumeManagerCore app. # Third-Party Imports. from django.db.models import Q +from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.mixins import LoginRequiredMixin -from django.shortcuts import render -from django.views.generic import ListView, UpdateView +from django.shortcuts import get_object_or_404, redirect +from django.views.generic import DetailView, ListView, UpdateView +from django.urls import reverse # Internal Imports. -from .models import Resume +from .models import ( + Resume, + ResumeSection, + ResumeToSectionIntermediary, + DataSectionHeader, + DataKeyValue, + DataItem, +) class Index(LoginRequiredMixin, ListView): @@ -25,14 +34,130 @@ class Index(LoginRequiredMixin, ListView): # Return only resumes that are under example user or current user. example_user = get_user_model().objects.get(username='example_user') - resumes = Resume.objects.filter(user__in=[example_user, self.request.user]) + return Resume.objects.filter(user__in=[example_user, self.request.user]) - print('\n\n\n\n') - print('pulled resumes:') - print('{0}'.format(resumes)) - print('\n') - print('all resumes:') - print('{0}'.format(Resume.objects.all())) - print('\n\n\n\n') - return resumes +class DisplayResume(LoginRequiredMixin, DetailView): + """Resume display view.""" + + model = Resume + template_name = 'resume_manager/display.html' + + def dispatch(self, request, *args, **kwargs): + """ + Normally dispatch is left alone. However, here we override to make sure the user has + permission to access resume before proceeding. + + If true, we call the original, inherited dispatch that proceeds to call the rest of the class logic. + If false, we instead redirect to the resume index page for user. + """ + + # Pull url kwarg data. + return_redirect = False + self.resume_pk = kwargs['pk'] + + # Check if resume with pk exists. + try: + self.resume = Resume.objects.get(pk=self.resume_pk) + except Resume.DoesNotExist: + # Failed to find corresponding resume with pk from url. Fallback to index. + return_redirect = True + messages.warning(request, 'Invalid resume. Redirecting to index.') + + if not return_redirect: + # Resume was valid. Now ensure user is meant to access it. + example_user = get_user_model().objects.get(username='example_user') + if not (self.resume.user == example_user or self.resume.user == self.request.user): + # Resume exists, but user doesn't have permission to access. Fallback to index. + return_redirect = True + messages.warning(request, 'Invalid resume. Redirecting to index.') + + # Handle if redirect. + if return_redirect: + return redirect(reverse('resume_manager_core:index')) + + # If we made it this far, then url was valid for user. + # Proceed to rest of view logic. + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + """Add additional context (variables) for template to display.""" + + # Call parent logic. + context = super().get_context_data(**kwargs) + + # Get all intermediary relations for resume. + resume_intermediaries = ResumeToSectionIntermediary.objects.filter( + resume=self.resume, + ) + + # Organize into parse-able data. + resume_data = [] + resume_format_page = ResumeSection.objects.get(name='Page') + resume_pages = resume_intermediaries.filter( + section_format=resume_format_page, + ) + # Add each page as its own unique base element in dataset. + for resume_page in resume_pages: + + # Get all related children. + section_children = resume_intermediaries.filter( + parent_section=resume_page + ) + + # Build sub-dictionaries for children. + section_children_data = [] + for child in section_children: + section_children_data.append(self.build_resume_data(child, resume_intermediaries)) + + # Organize data value. + resume_data.append({ + 'section': resume_page, + 'section_children': section_children_data, + }) + + # Update template context. + context.update({ + 'resume': self.resume, + 'resume_theme': self.resume.theme, + 'resume_data': resume_data, + }) + + # Return updated context. + return context + + def build_resume_data(self, current_section, all_sections): + """Recursive function to format resume data structure for template output.""" + # Get all related children. + section_children = all_sections.filter( + parent_section=current_section + ) + + # Build sub-dictionaries for children. + section_children_data = [] + for child in section_children: + section_children_data.append(self.build_resume_data(child, all_sections)) + + # Get all headers for section. + section_header = DataSectionHeader.objects.filter( + resume_section=current_section, + ) + + # Get all key-value pairs for section. + section_key_value_pairs = DataKeyValue.objects.filter( + resume_section=current_section, + ) + + # Get all items for section. + section_items = DataItem.objects.filter( + resume_section=current_section, + ) + + # Organize data value. + return { + 'section': current_section, + 'section_children': section_children_data, + 'section_header': section_header, + 'section_key_value_pairs': section_key_value_pairs, + 'section_items': section_items, + }