Prechádzať zdrojové kódy

Add the time_spent field (#10)

omassot 6 rokov pred
rodič
commit
63a479cd26

+ 2 - 1
main/forms.py

@@ -52,7 +52,7 @@ class StoryForm(forms.ModelForm):
     class Meta:
         model = Story
         widgets = {'author': forms.HiddenInput()}
-        fields = ('epic', 'author', 'name', 'weight', 'story_type', 'description', 'assignees', 'sprints')
+        fields = ('epic', 'author', 'name', 'weight', 'time_spent', 'story_type', 'description', 'assignees', 'sprints')
         
     def __init__(self, *args, **kwargs):
         super(StoryForm, self).__init__(*args, **kwargs)
@@ -60,6 +60,7 @@ class StoryForm(forms.ModelForm):
         self.fields['assignees'].required = False
         self.fields['sprints'].required = False
         self.fields['weight'].required = False
+        self.fields['time_spent'].required = False
         self.fields['story_type'].required = False
     
     def save(self, *args, **kwargs):

+ 2 - 0
main/models.py

@@ -212,6 +212,8 @@ class Story(BaseModel):
     story_type = models.IntegerField(default=0, choices=STORY_TYPE, verbose_name="Type")
     description = MartorField(blank=True, default="", verbose_name="Description")
     closed = models.BooleanField(default=False, verbose_name="Clôturée")
+    time_spent = models.FloatField(default=0, verbose_name="Temps passé (en 1/2 journées)")
+    
     author = models.ForeignKey(User, 
                                on_delete=models.PROTECT, 
                                null=True, blank=True,

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

@@ -329,6 +329,42 @@ $(document).ready( function () {
             }
         });
 	});
+	
+	$("#sprint-end-table").on('submit', 'form', function(event) {
+		event.preventDefault();
+		
+		var action = $(this).attr('action');
+		var val_field = $(this).find('input[name=time_spent]');
+		
+		console.log(action);
+		console.log(val_field.val())
+		
+		$.ajax({
+			type: "POST",
+			url: action,
+			data : { time_spent : val_field.val() },
+			success: function (response) {
+				val_field.animate({ borderColor: "#009933" }, 1500, function() {
+					                val_field.removeAttr('style'); } );
+			},
+			failure: function (response) {
+				val_field.animate({ borderColor: "#ff0000" }, 1500, function() {
+	                val_field.removeAttr('style'); } );
+				console.log('Ajax failure: ' + response.responseText);
+				$("#sprint-end-table").unbind('submit');   // Ajax is not working: unbind
+				$(this).submit();
+			},
+			
+			error: function (response) {
+				val_field.animate({ borderColor: "#ff0000" }, 1500, function() {
+	                val_field.removeAttr('style'); } );
+				console.log('Ajax error: ' + response.responseText);
+				$("#sprint-end-table").unbind('submit');   // Ajax is not working: unbind
+				$(this).submit();
+			}
+		});
+	
+	});
 
 	$("#sprint-end .retro-section input").click(function (event) {
 		if (confirm('Le sprint courant va être marqué comme terminé, continuer?')) {

+ 9 - 1
main/templates/sprint_end.html

@@ -25,7 +25,7 @@
 	{% else %}
 	
 		<h3>Clore les stories</h3>
-		<table>
+		<table id="sprint-end-table">
 		{% for story in sprint.stories.all|dictsort:"id" %}
 			<tr data-id="{{ story.id }}">
 				<td class="btn-cell" width="1%">
@@ -51,6 +51,14 @@
 				
 				<td class="annotation">{% if story.epic_id %}<span>{{ story.epic.name }}</span>{% endif %}</td>
 				<td width="1%">{% if story.weight %}<span>{% include 'weight_svg.html' with weight=story.weight h=20 %}</span>{% endif %}</td>
+				<td width="10%">
+				    <form action="{% url 'story_time_spent_update' story_id=story.id %}" 
+				          method="post" accept-charset="utf-8" 
+				          style="margin: 0;">
+						{% csrf_token %}
+			    	    <input class="raw-input" name="time_spent" type="text" value="{{ story.time_spent }}" title="Temps passé (en 1/2 journées)">
+				    </form>
+				</td>
 			</tr>
 		{% endfor %}
 		</table>

+ 12 - 1
main/templates/story_details.html

@@ -92,7 +92,18 @@
 		{% endfor %}
 		</ul>
 	</div>
-	
+	{% if story.closed or story.time_spent > 0 %}
+		<div class="flex-row">
+			<h5>Temps passé</h5>
+			<span style="margin-left: 21px;">
+			    {% if story.time_spent > 0 %}
+			        {{ story.time_spent }} demi-journées
+			    {% else %}
+			        <i>Non-renseigné</i>
+			    {% endif %}
+			</span>
+		</div>
+	{% endif %}
 	<hr>
 	
 	{% comments_for story %}

+ 8 - 0
main/templates/story_form.html

@@ -54,6 +54,14 @@
 		    {{ form.weight.label_tag }}
 		    {{ form.weight }}
 		</p>
+			
+		{% if form.instance.id %}
+			<p>
+			    {{ form.time_spent.errors }}
+			    {{ form.time_spent.label_tag }}
+			    {{ form.time_spent }}
+			</p>
+		{% endif %}
 		
 		<p>
 		    {{ form.description.errors }}

+ 1 - 0
main/urls.py

@@ -30,6 +30,7 @@ urlpatterns = [
     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('stories/close/<int:story_id>/', views.story_close, name='story_close'),
+    path('stories/timespent/<int:story_id>/', views.story_time_spent_update, name='story_time_spent_update'),
     path('stories/reopen/<int:story_id>/', views.story_reopen, name='story_reopen'),
     path('stories/reaffect/<int:story_id>/', views.story_reaffect, name='story_reaffect_ajax'),
     path('epics/<int:epic_id>', views.epic_details, name='epic_details'),

+ 12 - 0
main/views.py

@@ -269,6 +269,18 @@ def story_close(request, story_id):
     else:
         return redirect(request.META['HTTP_REFERER'])
 
+@login_required
+def story_time_spent_update(request, story_id):
+    if request.method == 'POST':
+        story = get_object_or_404(Story, id=story_id)
+        story.time_spent = request.POST.get('time_spent')
+        story.save()
+            
+        if request.is_ajax():
+            return HttpResponse(story.to_json())
+        else:
+            return redirect("sprint_end")
+
 @login_required
 def story_reaffect(request, story_id):
     story = get_object_or_404(Story, id=story_id)