瀏覽代碼

Ajout du module Epics et refonte de l'index

olivier.massot 7 年之前
父節點
當前提交
7cb2aa4644

+ 10 - 9
.gitignore

@@ -1,9 +1,10 @@
-*.pyc
-.project
-.pydevproject
-.settings/
-*.log
-*.log.1
-/work/*
-tmp*
-~$*
+*.pyc
+.project
+.pydevproject
+.settings/
+*.log
+*.log.1
+/work/*
+tmp*
+~$*
+.sqlite3

+ 29 - 21
main/forms.py

@@ -1,22 +1,30 @@
-'''
-
-@author: olivier.massot, 2018
-'''
-from django import forms
-from martor.fields import MartorFormField
-
-from main.models import Story
-
-
-class StoryForm(forms.ModelForm):
-    
-    description = MartorFormField(label="Description")
- 
-    class Meta:
-        model = Story
-        fields = ('epic', 'name', 'description', 'assignees', 'sprints')
-        
-    def __init__(self, *args, **kwargs):
-        super(StoryForm, self).__init__(*args, **kwargs)
-        self.fields['assignees'].required = False
+'''
+
+@author: olivier.massot, 2018
+'''
+from django import forms
+from martor.fields import MartorFormField
+
+from main.models import Story, Epic
+
+
+class EpicForm(forms.ModelForm):
+    
+    description = MartorFormField(label="Description")
+ 
+    class Meta:
+        model = Epic
+        fields = ('project', 'name', 'size', 'value', 'description')
+        
+class StoryForm(forms.ModelForm):
+    
+    description = MartorFormField(label="Description")
+ 
+    class Meta:
+        model = Story
+        fields = ('epic', 'name', 'description', 'assignees', 'sprints')
+        
+    def __init__(self, *args, **kwargs):
+        super(StoryForm, self).__init__(*args, **kwargs)
+        self.fields['assignees'].required = False
         self.fields['sprints'].required = False

+ 15 - 12
main/models.py

@@ -46,11 +46,11 @@ class Epic(BaseModel):
              ("XL", "XL (4-6 sprints)"), 
              ("XXL", "XXL  (> 6 sprints)"))
     
-    name = models.CharField(max_length=200, default="")
-    size = models.CharField(max_length=10, default="M", choices=SIZES)
-    value = models.IntegerField(default=0)
-    description = models.TextField(default="", blank=True)
-    project = models.ForeignKey(Project, on_delete=models.PROTECT, null=True)
+    name = models.CharField(max_length=200, default="", verbose_name="Nom")
+    size = models.CharField(max_length=10, default="M", choices=SIZES, verbose_name="Taille")
+    value = models.IntegerField(default=0, verbose_name="Valeur")
+    description = models.TextField(default="", blank=True, verbose_name="Description")
+    project = models.ForeignKey(Project, on_delete=models.PROTECT, null=True, verbose_name="Projet")
     
     def __str__(self):
         return self.name
@@ -71,22 +71,25 @@ class Story(BaseModel):
         verbose_name = "story"
         verbose_name_plural = "stories"
         ordering = ('-updated', )
-    epic = models.ForeignKey(Epic, on_delete=models.PROTECT, null=True)
-    name = models.CharField(max_length=200, default="")
-    description = MartorField()
-    closed = models.BooleanField(default=False)
+    epic = models.ForeignKey(Epic, on_delete=models.PROTECT, null=True, related_name="stories", verbose_name="Epic")
+    name = models.CharField(max_length=200, default="", verbose_name="Nom")
+    description = MartorField(verbose_name="Description")
+    closed = models.BooleanField(default=False, verbose_name="Clôturée")
     author = models.ForeignKey(User, 
                                on_delete=models.PROTECT, 
                                null=True,
                                related_name="stories",
-                               related_query_name="story")
+                               related_query_name="story", 
+                               verbose_name="Auteur")
     
     assignees = models.ManyToManyField(User, 
-                                       related_name="assigned")
+                                       related_name="assigned", 
+                                       verbose_name="Assignés")
     
     sprints = models.ManyToManyField(Sprint, 
                                        related_name="stories",
-                                       related_query_name="story")
+                                       related_query_name="story", 
+                                       verbose_name="Sprints")
     
     def __str__(self):
         return self.name

+ 12 - 0
main/static/css/custom.css

@@ -36,6 +36,10 @@
     justify-content: flex-end;
 }
 
+.flex-space-around {
+	justify-content: space-around;
+}
+
 .hidden {
     display: none;
 }
@@ -83,6 +87,14 @@ select[multiple] {
 	margin-left: 1em;
 }
 
+[data-url] {
+	cursor: pointer; 
+}
+
+[data-url]:hover{
+	background-color: #b3d9ff;
+}
+
 /* Index */
 
 .issue-title a {

+ 5 - 1
main/static/css/templated.css

@@ -1856,9 +1856,10 @@
 	input[type="password"],
 	input[type="email"],
 	input[type="tel"],
+	input[type="number"],
 	select,
 	textarea {
-		-moz-appearance: none;
+		/* -moz-appearance: none; */
 		-webkit-appearance: none;
 		-ms-appearance: none;
 		appearance: none;
@@ -1878,6 +1879,7 @@
 		input[type="password"]:invalid,
 		input[type="email"]:invalid,
 		input[type="tel"]:invalid,
+		input[type="number"]:invalid,
 		select:invalid,
 		textarea:invalid {
 			box-shadow: none;
@@ -1887,6 +1889,7 @@
 		input[type="password"]:focus,
 		input[type="email"]:focus,
 		input[type="tel"]:focus,
+		input[type="number"]:focus,
 		select:focus,
 		textarea:focus {
 			border-color: #25a2c3;
@@ -1929,6 +1932,7 @@
 	input[type="text"],
 	input[type="password"],
 	input[type="email"],
+	input[type="number"],
 	select {
 		height: 3.5em;
 		line-height: 3.5em;

+ 6 - 0
main/static/js/custom.js

@@ -0,0 +1,6 @@
+$(document).ready( function () {
+	
+	$('[data-url]').on('click', function() {
+		window.location.href = $(this).data('url');
+	});
+});

+ 83 - 80
main/templates/_layout.html

@@ -1,81 +1,84 @@
-
-{% load staticfiles %}
-<!DOCTYPE html>
-<html lang="fr">
-	<head>
-	    <title>Manche Numérique SIG - Backlog</title>
-	    <meta charset="utf-8" />
-	    
-		<link rel="stylesheet" type="text/css" href="{% static 'css/templated.css' %}"/>
-		<link rel="stylesheet" type="text/css" href="{% static 'css/custom.css' %}"/>
-
-		<!-- From Martor plugin -->
-		<link href="{% static 'plugins/css/ace.min.css' %}" type="text/css" media="all" rel="stylesheet" />
-		<link href="{% static 'plugins/css/semantic.min.css' %}" type="text/css" media="all" rel="stylesheet" />
-		<link href="{% static 'plugins/css/resizable.min.css' %}" type="text/css" media="all" rel="stylesheet" />
-		<link href="{% static 'martor/css/martor.min.css' %}" type="text/css" media="all" rel="stylesheet" />
-
-		<script src="{% static 'js/jquery.min.js' %}"></script>
-		<script src="{% static 'js/jquery.scrolly.min.js' %}"></script>
-		<script src="{% static 'js/skel.min.js' %}"></script>
-		<script src="{% static 'js/util.js' %}"></script>
-		<script src="{% static 'js/templated.js' %}"></script>
-		
-		<!-- From Martor plugin -->
-		<script type="text/javascript" src="{% static 'plugins/js/ace.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/semantic.min.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/mode-markdown.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/ext-language_tools.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/theme-github.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/highlight.min.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/resizable.min.js' %}"></script>
-		<script type="text/javascript" src="{% static 'plugins/js/emojis.min.js' %}"></script>
-		<script type="text/javascript" src="{% static 'martor/js/martor.min.js' %}"></script>
-		
-	</head>
-	
-	<body class="wrapper">
-		<header id="header">
-			<nav class="left">
-				<a href="#menu"><span>Menu</span></a>
-			</nav>
-			
-			<a href="{% url 'index' %}" class="logo">SIG Backlog</a>
-			
-	        <nav class="right">
-	        	{% if request.user.is_authenticated %}
-	        		<div class="user-panel">
-	        			<i class="fa fa-user"></i> <span>{{ request.user.first_name }} {{ request.user.last_name }}</span>
-	        		</div>
-	        	{% endif %}
-			</nav>
-		</header>
-		
-	    <!-- Menu -->
-		<nav id="menu">
-			<ul class="links">
-				<li><a href="{% url 'index' %}">Accueil</a></li>
-				<li><a href="">Projets</a></li>
-				<li><a href="{% url 'admin:index' %}">Administration</a></li>
-			</ul>
-			<ul class="actions vertical">
-				<li><a href="#" class="button fit">Login</a></li>
-			</ul>
-		</nav>
-		
-		<section id="main" class="wrapper">
-			<div class="inner">
-			{% block main %}
-			
-			{% endblock %}
-			</div>
-		</section>
-		
-		<!-- 	    
-		<footer id="footer">
-	    	Manche Numérique, 2018 - <a href="">Contact</a>
-    	</footer> 
-    	-->
-    
-	</body>
+
+{% load staticfiles %}
+<!DOCTYPE html>
+<html lang="fr">
+	<head>
+	    <title>Manche Numérique SIG - Backlog</title>
+	    <meta charset="utf-8" />
+
+		<link rel="stylesheet" type="text/css" href="{% static 'css/templated.css' %}"/>
+		
+		<!-- From Martor plugin -->
+		<link href="{% static 'plugins/css/ace.min.css' %}" type="text/css" media="all" rel="stylesheet" />
+		<link href="{% static 'plugins/css/semantic.min.css' %}" type="text/css" media="all" rel="stylesheet" />
+		<link href="{% static 'plugins/css/resizable.min.css' %}" type="text/css" media="all" rel="stylesheet" />
+		<link href="{% static 'martor/css/martor.min.css' %}" type="text/css" media="all" rel="stylesheet" />
+	    
+		<link rel="stylesheet" type="text/css" href="{% static 'css/custom.css' %}"/>
+
+		<script src="{% static 'js/jquery.min.js' %}"></script>
+		<script src="{% static 'js/jquery.scrolly.min.js' %}"></script>
+		<script src="{% static 'js/skel.min.js' %}"></script>
+		<script src="{% static 'js/util.js' %}"></script>
+		<script src="{% static 'js/templated.js' %}"></script>
+		
+		<!-- From Martor plugin -->
+		<script type="text/javascript" src="{% static 'plugins/js/ace.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/semantic.min.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/mode-markdown.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/ext-language_tools.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/theme-github.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/highlight.min.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/resizable.min.js' %}"></script>
+		<script type="text/javascript" src="{% static 'plugins/js/emojis.min.js' %}"></script>
+		<script type="text/javascript" src="{% static 'martor/js/martor.min.js' %}"></script>
+		
+		<script src="{% static 'js/custom.js' %}"></script>
+		
+	</head>
+	
+	<body class="wrapper">
+		<header id="header">
+			<nav class="left">
+				<a href="#menu"><span>Menu</span></a>
+			</nav>
+			
+			<a href="{% url 'index' %}" class="logo">SIG Backlog</a>
+			
+	        <nav class="right">
+	        	{% if request.user.is_authenticated %}
+	        		<div class="user-panel">
+	        			<i class="fa fa-user"></i> <span>{{ request.user.first_name }} {{ request.user.last_name }}</span>
+	        		</div>
+	        	{% endif %}
+			</nav>
+		</header>
+		
+	    <!-- Menu -->
+		<nav id="menu">
+			<ul class="links">
+				<li><a href="{% url 'index' %}">Accueil</a></li>
+				<li><a href="">Projets</a></li>
+				<li><a href="{% url 'admin:index' %}">Administration</a></li>
+			</ul>
+			<ul class="actions vertical">
+				<li><a href="#" class="button fit">Login</a></li>
+			</ul>
+		</nav>
+		
+		<section id="main" class="wrapper">
+			<div class="inner">
+			{% block main %}
+			
+			{% endblock %}
+			</div>
+		</section>
+		
+		<!-- 	    
+		<footer id="footer">
+	    	Manche Numérique, 2018 - <a href="">Contact</a>
+    	</footer> 
+    	-->
+    
+	</body>
 </html>

+ 65 - 0
main/templates/epic_details.html

@@ -0,0 +1,65 @@
+{% extends '_layout.html' %}
+
+{% block main %}
+	{% load martortags %}
+	
+	<header>
+		<div class="flex-row">
+			<h2 class="flex-extend">{{ epic.name }}</h2>
+		
+			<ul class="actions small">
+				<li><a class="button special icon fa-edit tool-btn" href="{% url 'epic_edit' epic_id=epic.id %}"></a></li>
+				<li><a class="button icon fa-trash tool-btn" href="{% url 'epic_delete' epic_id=epic.id %}"></a></li>
+			</ul>
+		</div>
+		
+	</header>
+
+	<div class="flex-row flex-space-around">
+		<span>Taille: <b>{{ epic.size }}</b></span>
+		<span>Valeur: <b>{{ epic.value }}</b></span>
+	</div>
+	
+	<hr>
+	
+	<div class="flex-row">
+		<h4 class="flex-extend">Stories</h4>
+		<a class="button special icon fa-plus tool-btn" href="{% url 'story_create' epic_id=epic.id %}"></a>
+	</div>
+	
+	{% if epic.stories.count %}
+	    <ul class="alt issues-list">
+	    {% for story in epic.stories.all %}
+	    
+		    <li id="story_{{ story_id }}" class="issue" data-id="{{ story_id }}">
+		    	<div class="issue-frame flex-row">
+		    		<div class="issue-frame-left flex-extend">
+		    			<div class="issue-title">
+		        			<a href="{% url 'story_details' story_id=story.id %}">{{ story.name }}</a>
+		        		</div>
+		        		<div class="issue-infos">
+		        			<span class="annotation">Créée le {{ story.created }}, par {{ story.author.username }}</span>
+		        		</div>
+		        	</div>
+		        	<div class="issue-frame-right">
+		        		<ul class="controls">
+		        		<!-- Icone commentaires ici -->
+		        		</ul>
+		        		<div class="annotation issue-last-update">Modifié le {{ story.updated }}</div>
+		        	</div>
+		        </div>
+		    </li>
+	    
+	    {% endfor %}
+	    </ul>
+	 {% else %}
+	 	<span class="annotation">(Aucune story)</span>
+	 {% endif %}
+	
+	<hr>
+	
+	<div class="description">
+		{{ epic.description|safe_markdown }}
+	</div>
+
+{% endblock %}

+ 25 - 0
main/templates/epic_form.html

@@ -0,0 +1,25 @@
+{% extends '_layout.html' %}
+
+{% block main %}
+	
+	<h2>{% if form.instance.id %}Edition de l'Epic{% else %}Nouvelle Epic{% endif %}</h2>
+	<form action="." method="post" enctype="multipart/form-data">
+	{% csrf_token %}
+	
+	{{ form.as_p }}
+
+	<div class="flex-row flex-end">
+		<span style="margin-right: 1em;">
+		{% if form.instance.id %}
+			<a class="button alt" href="{% url 'epic_details' epic_id=form.instance.id %}">Annuler</a>
+		{% else %}
+			<a class="button alt" href="{% url 'index' %}">Annuler</a>
+		{% endif %}
+		</span>
+		
+		<input type="submit" value="Enregistrer">
+	</div>
+	
+	</form>
+		
+{% endblock %}

+ 41 - 37
main/templates/index.html

@@ -1,38 +1,42 @@
-{% extends '_layout.html' %}
-
-{% block main %}
-
-	<header class="flex-row">
-		<h3 class="flex-extend">Mes Stories</h3>
-	 	<a class="button special small" href="{% url 'story_create' %}"><i class="fa fa-plus"></i>Nouvelle Story</a>
-	</header>
-	{% if mes_stories %}
-	    <ul class="alt issues-list">
-	    {% for story in mes_stories %}
-	    
-		    <li id="story_{{ story_id }}" class="issue" data-id="{{ story_id }}">
-		    	<div class="issue-frame flex-row">
-		    		<div class="issue-frame-left flex-extend">
-		    			<div class="issue-title">
-		        			<a href="{% url 'story_details' story_id=story.id %}">{{ story.name }}</a>
-		        		</div>
-		        		<div class="issue-infos">
-		        			<span class="annotation">Créée le {{ story.created }}, par {{ story.author.username }}</span>
-		        		</div>
-		        	</div>
-		        	<div class="issue-frame-right">
-		        		<ul class="controls">
-		        		<!-- Icone commentaires ici -->
-		        		</ul>
-		        		<div class="annotation issue-last-update">Modifié le {{ story.updated }}</div>
-		        	</div>
-		        </div>
-		    </li>
-	    
-	    {% endfor %}
-	    </ul>
-	 {% else %}
-	 	<span class="annotation">(Aucune story)</span>
-	 {% endif %}
-	 
+{% extends '_layout.html' %}
+
+{% block main %}
+
+
+<header>
+	<div class="flex-row">
+		<h2 class="flex-extend">Backlog</h2>
+		<a class="button icon fa-plus tool-btn" href="{% url 'epic_create' %}"></a>
+	</div>
+
+</header>
+
+<table class="alt">
+	
+	<thead>
+		<th>Epic</th>
+		<th>Projet</th>
+		<th>Dim.</th>
+		<th>Val.</th>
+		<th>Stories</th>
+	
+	</thead>
+	
+	<tbody>
+	
+		{% for epic in epics %}
+		<tr data-url="{% url 'epic_details' epic_id=epic.id %}">
+			<td><h5>{{ epic.name }}</h5></td>
+			<td>{{ epic.project.name }}</td>
+			<td>{{ epic.size }}</td>
+			<td>{{ epic.value }}</td>
+			<td>{{ epic.stories.count }}</td>
+		</tr>
+		{% endfor %}
+	
+	
+	</tbody>
+
+</table>
+
 {% endblock %}

+ 4 - 4
main/templates/story_details.html

@@ -4,7 +4,10 @@
 	{% load martortags %}
 	
 	<header>
-		<blockquote class="annotation">Créée par {{ story.author.get_full_name }}, le {{ story.created }}</blockquote>
+		<div class="flex-row">
+			<blockquote class="flex-extend"><a href="{% url 'epic_details' epic_id=story.epic.id %}"><b><i class="fa fa-backward"></i>Retour à {{ story.epic.name }}</b></a></blockquote>
+			<blockquote class="annotation">Créée par {{ story.author.get_full_name }}, le {{ story.created }}</blockquote>
+		</div>
 		<div class="flex-row">
 			<h2 class="flex-extend">{{ story.name }}</h2>
 		
@@ -13,7 +16,6 @@
 				<li><a class="button icon fa-trash tool-btn" href="{% url 'story_delete' story_id=story.id %}"></a></li>
 			</ul>
 		</div>
-		
 	</header>
 	
 	<hr>
@@ -45,6 +47,4 @@
 		</ul>
 	</div>
 	
-	
-	
 {% endblock %}

+ 8 - 1
main/templates/story_form.html

@@ -2,13 +2,20 @@
 
 {% block main %}
 	
-	<h2>Nouvelle Story</h2>
+	<h2>{% if form.instance.id %}Edition de la Story{% else %}Nouvelle Story{% endif %}</h2>
 	<form action="." method="post" enctype="multipart/form-data">
 	{% csrf_token %}
 	
 	{{ form.as_p }}
 
 	<div class="flex-row flex-end">
+		<span style="margin-right: 1em;">
+		{% if form.instance.id %}
+			<a class="button alt" href="{% url 'story_details' story_id=form.instance.id %}">Annuler</a>
+		{% else %}
+			<a class="button alt" href="{% url 'story_index' %}">Annuler</a>
+		{% endif %}
+		</span>
 		<input type="submit" value="Enregistrer">
 	</div>
 	

+ 40 - 0
main/templates/story_index.html

@@ -0,0 +1,40 @@
+{% extends '_layout.html' %}
+
+{% block main %}
+
+	<header class="flex-row">
+		<h3 class="flex-extend">Stories</h3>
+	 	<a class="button small icon fa-plus tool-btn" href="{% url 'story_create' %}">Nouvelle Story</a>
+	</header>
+
+	{% if stories %}
+	    <ul class="alt issues-list">
+	    {% for story in stories %}
+	    
+		    <li id="story_{{ story_id }}" class="issue" data-id="{{ story_id }}">
+		    	<div class="issue-frame flex-row">
+		    		<div class="issue-frame-left flex-extend">
+		    			<div class="issue-title">
+		        			<a href="{% url 'story_details' story_id=story.id %}">{{ story.name }}</a>
+		        		</div>
+		        		<div class="issue-infos">
+		        			<span class="annotation">Créée le {{ story.created }}, par {{ story.author.username }}</span>
+		        		</div>
+		        	</div>
+		        	<div class="issue-frame-right">
+		        		<ul class="controls">
+		        		<!-- Icone commentaires ici -->
+		        		</ul>
+		        		<div class="annotation issue-last-update">Modifié le {{ story.updated }}</div>
+		        	</div>
+		        </div>
+		    </li>
+	    
+	    {% endfor %}
+	    </ul>
+	 {% else %}
+	 	<span class="annotation">(Rien à afficher)</span>
+	 {% endif %}
+
+	 
+{% endblock %}

+ 21 - 16
main/urls.py

@@ -1,17 +1,22 @@
-'''
-
-@author: olivier.massot, 2018
-'''
-
-from django.urls import path
-
-from . import views
-
-
-urlpatterns = [
-    path('', views.index, name='index'),
-    path('stories/<int:story_id>', views.story_details, name='story_details'),
-    path('stories/create/', views.story_create, name='story_create'),
-    path('stories/edit/<int:story_id>/', views.story_edit, name='story_edit'),
-    path('stories/delete/<int:story_id>', views.story_delete, name='story_delete'),
+'''
+
+@author: olivier.massot, 2018
+'''
+
+from django.urls import path
+
+from . import views
+
+
+urlpatterns = [
+    path('', views.index, name='index'),
+    path('stories/', views.story_index, name='story_index'),
+    path('stories/<int:story_id>', views.story_details, name='story_details'),
+    path('stories/create/<int:epic_id>/', views.story_create, name='story_create'),
+    path('stories/edit/<int:story_id>/', views.story_edit, name='story_edit'),
+    path('stories/delete/<int:story_id>/', views.story_delete, name='story_delete'),
+    path('epics/<int:epic_id>', views.epic_details, name='epic_details'),
+    path('epics/create/', views.epic_create, name='epic_create'),
+    path('epics/edit/<int:epic_id>/', views.epic_edit, name='epic_edit'),
+    path('epics/delete/<int:epic_id>/', views.epic_delete, name='epic_delete'),
 ]

+ 100 - 60
main/views.py

@@ -1,61 +1,101 @@
-from django.contrib.auth.models import User
-from django.core.exceptions import ObjectDoesNotExist
-from django.http.response import Http404, HttpResponseRedirect
-from django.shortcuts import render, get_object_or_404, redirect
-
-from main.forms import StoryForm
-from main.models import Story
-
-
-def index(request):
-    try:
-        user = User.objects.get(username=request.user)
-        user_stories = list(user.stories.all())
-    except ObjectDoesNotExist:
-        user_stories = []
-    
-    return render(request, 'index.html', {'mes_stories': user_stories})
-
-def story_details(request, story_id):
-    story = get_object_or_404(Story, id=story_id)
-    return render(request, 'story_details.html', {'story': story})
-
-def story_create(request):
-    if request.method == 'POST':
-        
-        form = StoryForm(request.POST)
-        if form.is_valid():
-            story = form.save(commit=False)
-            story.author = User.objects.get(username=request.user)
-            story.save()
-            return redirect("story_details", story.id)
-        
-    else:
-        form = StoryForm()
-         
-    return render(request, 'story_form.html', {'form': form})
-
-def story_edit(request, story_id):
-    if request.method == 'POST':
-        story = get_object_or_404(Story, id=story_id)
-        form = StoryForm(request.POST, instance=story)
-        if form.is_valid():
-            form.save()
-            return redirect("story_details", story.id)
-        
-    else:
-        story = get_object_or_404(Story, id=story_id)
-        form = StoryForm(instance=story)
-        
-    return render(request, 'story_form.html', {'form': form})
-
-def story_delete(request, story_id):
-    if request.method == 'POST':
-        story = Story.objects.get(id=story_id)
-        story.delete()
-        return redirect("index")
-    else:
-        story = Story.objects.get(id=story_id)
-        return render(request, 'deletion.html', {'object': story})
-    
+from django.contrib.auth.models import User
+from django.shortcuts import render, get_object_or_404, redirect
+
+from main.forms import StoryForm, EpicForm
+from main.models import Story, Epic
+
+
+def index(request):
+    epics = Epic.objects.all()
+    return render(request, 'index.html', {'epics': epics})
+
+def story_index(request):
+    stories = Story.objects.all()
+    return render(request, 'story_index.html', {'stories': stories})
+
+def story_details(request, story_id):
+    story = get_object_or_404(Story, id=story_id)
+    return render(request, 'story_details.html', {'story': story})
+
+def story_create(request, epic_id):
+    if request.method == 'POST':
+        
+        form = StoryForm(request.POST)
+        if form.is_valid():
+            story = form.save(commit=False)
+            story.author = User.objects.get(username=request.user)
+            story.save()
+            return redirect("story_details", story.id)
+        
+    else:
+        story = Story()
+        story.epic = get_object_or_404(Epic, id=epic_id)
+        form = StoryForm(instance=story)
+         
+    return render(request, 'story_form.html', {'form': form})
+
+def story_edit(request, story_id):
+    if request.method == 'POST':
+        story = get_object_or_404(Story, id=story_id)
+        form = StoryForm(request.POST, instance=story)
+        if form.is_valid():
+            form.save()
+            return redirect("story_details", story.id)
+        
+    else:
+        story = get_object_or_404(Story, id=story_id)
+        form = StoryForm(instance=story)
+        
+    return render(request, 'story_form.html', {'form': form})
+
+def story_delete(request, story_id):
+    if request.method == 'POST':
+        story = Story.objects.get(id=story_id)
+        story.delete()
+        return redirect("index")
+    else:
+        story = Story.objects.get(id=story_id)
+        return render(request, 'deletion.html', {'object': story})
+    
+def epic_details(request, epic_id):
+    epic = get_object_or_404(Epic, id=epic_id)
+    return render(request, 'epic_details.html', {'epic': epic})
+
+def epic_create(request):
+    if request.method == 'POST':
+        
+        form = EpicForm(request.POST)
+        if form.is_valid():
+            epic = form.save(commit=False)
+            epic.author = User.objects.get(username=request.user)
+            epic.save()
+            return redirect("epic_details", epic.id)
+        
+    else:
+        form = EpicForm()
+         
+    return render(request, 'epic_form.html', {'form': form})
+
+def epic_edit(request, epic_id):
+    if request.method == 'POST':
+        epic = get_object_or_404(Epic, id=epic_id)
+        form = EpicForm(request.POST, instance=epic)
+        if form.is_valid():
+            form.save()
+            return redirect("epic_details", epic.id)
+        
+    else:
+        epic = get_object_or_404(Epic, id=epic_id)
+        form = EpicForm(instance=epic)
+        
+    return render(request, 'epic_form.html', {'form': form})
+
+def epic_delete(request, epic_id):
+    if request.method == 'POST':
+        epic = Epic.objects.get(id=epic_id)
+        epic.delete()
+        return redirect("index")
+    else:
+        epic = Epic.objects.get(id=epic_id)
+        return render(request, 'deletion.html', {'object': epic})