From 85ddb29ad87392f1bd1d74b9cfea66d98ffb517b Mon Sep 17 00:00:00 2001
From: Brandon Rodriguez <brodriguez8774@gmail.com>
Date: Sat, 5 Aug 2023 19:46:44 -0400
Subject: [PATCH] Progress trying to figure out how to structure project logic

---
 .../resume_manager/resume_sections.json       |  26 ++-
 .../resume_manager_core__loadfixtures.py      | 127 +++++++++++---
 resume_manager_core/models.py                 |  36 ++--
 .../resume_manager_core/css/sass/base.scss    |  13 ++
 .../css/sass/src/_resume_blocky.scss          | 165 ++++++++++++++++++
 .../resume_manager/_display_recurse.html      |  49 ++++++
 .../_display_recurse_debug.html               |  12 ++
 .../templates/resume_manager/display.html     |  57 ++++++
 .../templates/resume_manager/index.html       |   8 +-
 resume_manager_core/urls.py                   |   1 +
 resume_manager_core/views.py                  | 149 ++++++++++++++--
 11 files changed, 597 insertions(+), 46 deletions(-)
 create mode 100644 resume_manager_core/static/resume_manager_core/css/sass/base.scss
 create mode 100644 resume_manager_core/static/resume_manager_core/css/sass/src/_resume_blocky.scss
 create mode 100644 resume_manager_core/templates/resume_manager/_display_recurse.html
 create mode 100644 resume_manager_core/templates/resume_manager/_display_recurse_debug.html
 create mode 100644 resume_manager_core/templates/resume_manager/display.html

diff --git a/resume_manager_core/fixtures/resume_manager/resume_sections.json b/resume_manager_core/fixtures/resume_manager/resume_sections.json
index 24d8543..f135735 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 089e4e7..89b4d3c 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 99c25ff..922945d 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 0000000..ef1dddb
--- /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 0000000..45a7fa9
--- /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 0000000..5af7c35
--- /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 0000000..76f1d94
--- /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 0000000..0dbd5bd
--- /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 99b5995..9d4fe14 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 8516ccb..1f75239 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 a4e9cae..6eeeb3d 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,
+        }
-- 
GitLab