You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2020/01/06 23:05:09 UTC

[allura] 01/04: [FEATURE] Added ForgeFeedback app

This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 66a29498d8099d5be31c3ea201d7e3f0f7c28a64
Author: Vrinda A <Vr...@in.bosch.com>
AuthorDate: Wed Sep 25 22:44:22 2019 +0530

    [FEATURE] Added ForgeFeedback app
    
     - This app will let the user rate and review a project.
---
 Allura/allura/model/project.py                     |   1 +
 Allura/allura/templates/jinja_master/nav_menu.html |  26 +++
 ForgeFeedback/forgefeedback/__init__.py            |   0
 ForgeFeedback/forgefeedback/feedback_main.py       | 230 +++++++++++++++++++++
 ForgeFeedback/forgefeedback/model/__init__.py      |  21 ++
 ForgeFeedback/forgefeedback/model/feedback.py      |  82 ++++++++
 .../forgefeedback/nf/feedback/css/feedback.css     | 112 ++++++++++
 ForgeFeedback/forgefeedback/templates/__init__.py  |   0
 .../templates/feedback/common_feedback.html        |  87 ++++++++
 .../templates/feedback/edit_feedback.html          | 103 +++++++++
 .../templates/feedback/feedback_list.html          | 100 +++++++++
 .../forgefeedback/templates/feedback/index.html    |  56 +++++
 .../templates/feedback/new_feedback.html           |  79 +++++++
 ForgeFeedback/forgefeedback/tests/__init__.py      |  17 ++
 .../forgefeedback/tests/functional/__init__.py     |  17 ++
 .../forgefeedback/tests/functional/test_root.py    |  69 +++++++
 .../forgefeedback/tests/test_feedback_roles.py     |  54 +++++
 ForgeFeedback/forgefeedback/tests/unit/__init__.py |  51 +++++
 .../forgefeedback/tests/unit/test_feedback.py      |  41 ++++
 .../tests/unit/test_root_controller.py             |  80 +++++++
 ForgeFeedback/forgefeedback/version.py             |  24 +++
 ForgeFeedback/setup.py                             |  53 +++++
 requirements.in                                    |   4 +-
 requirements.txt                                   |   2 +
 24 files changed, 1308 insertions(+), 1 deletion(-)

diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 0776e8f..5a6f896 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -251,6 +251,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
     tracking_id = FieldProperty(str, if_missing='')
     is_nbhd_project = FieldProperty(bool, if_missing=False)
     features = FieldProperty([str])
+    rating = FieldProperty(float, if_missing=0)
 
     # transient properties
     notifications_disabled = False
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 0f956ce..9509334 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -59,6 +59,32 @@
                 <a href="{{ admin.url() }}">{{ admin.username }}</a>{{ ',' if not loop.last }}
             {% endif %}
         {%- endfor -%}
+      <p id="detailed_ratings"><span id=stars></span></p>
     </div>
     {% endif %}
 {% endif %}
+
+<script>
+ document.getElementById("stars").innerHTML = getStars("{{c.project.rating}}");
+
+function getStars(ratings) {
+
+  // Round to nearest half
+  ratings = Math.round(ratings * 2) / 2;
+  let output = [];
+
+  // Append all the filled whole stars
+  for (var i = ratings; i >= 1; i--)
+    output.push('<i class="fa fa-star" aria-hidden="true" style="color: orange;"></i>&nbsp;');
+
+  // If there is a half a star, append it
+  if (i == .5) output.push('<i class="fa fa-star-half-o" aria-hidden="true" style="color: orange;"></i>&nbsp;');
+
+  // Fill the empty stars
+  for (let i = (5 - ratings); i >= 1; i--)
+    output.push('<i class="fa fa-star-o" aria-hidden="true" style="color: orange;"></i>&nbsp;');
+
+  return output.join('');
+
+ }
+ </script>
diff --git a/ForgeFeedback/forgefeedback/__init__.py b/ForgeFeedback/forgefeedback/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ForgeFeedback/forgefeedback/feedback_main.py b/ForgeFeedback/forgefeedback/feedback_main.py
new file mode 100644
index 0000000..0f9a555
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/feedback_main.py
@@ -0,0 +1,230 @@
+#	Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+import logging
+import pymongo
+
+# Non-stdlib imports
+
+from tg import expose, flash, url, config, request
+from tg.decorators import with_trailing_slash, without_trailing_slash
+from tg import tmpl_context as c, app_globals as g
+from ming.odm import session
+from ming.orm import session
+
+## profanityfilter package ##
+from profanityfilter import ProfanityFilter
+
+# Pyforge-specific imports
+
+from allura import model as M
+from allura import version
+from allura.app import (
+    Application,
+    )
+from allura.controllers import BaseController
+from allura.controllers.feed import FeedController
+from allura.lib.decorators import require_post
+from allura.lib.security import (require_access, has_access)
+from allura.model import project
+
+# Local imports
+
+from forgefeedback import model as TM
+from forgefeedback import version
+from forgefeedback.model import Feedback
+
+log = logging.getLogger(__name__)
+
+
+class ForgeFeedbackApp(Application):
+
+
+    __version__ = version.__version__
+    permissions = [
+        'read', 'update', 'create', 
+        'post', 'admin', 'delete'
+        ]
+
+    permissions_desc = {
+        'read': 'View ratings.',
+        'update': 'Edit ratings.',
+        'create': 'Create ratings.',
+        'admin': 'Set permissions. Configure option.',
+        'delete': 'Delete and undelete ratings. View deleted ratings.'
+    }
+
+    config_options = Application.config_options + [
+    ]
+    tool_label = 'Feedback'
+    tool_description = """
+        Feedbacks are given for the tools in the form of reviews and ratings, edit and delete the feedback"""
+    default_mount_label = 'Feedback'
+    default_mount_point = 'feedback'
+    ordinal = 8
+
+    def __init__(self, project, config):
+        Application.__init__(self, project, config)
+        self.root = RootController()
+   
+    def install(self, project):
+        'Set up any default permissions and roles here'
+        super(ForgeFeedbackApp, self).install(project)
+        # Setup permissions
+        role_admin = M.ProjectRole.by_name('Admin')._id
+        role_developer = M.ProjectRole.by_name('Developer')._id
+        role_auth = M.ProjectRole.by_name('*authenticated')._id
+        role_anon = M.ProjectRole.by_name('*anonymous')._id
+        self.config.acl = [
+            M.ACE.allow(role_anon, 'read'),
+            M.ACE.allow(role_auth, 'post'),
+            M.ACE.allow(role_auth, 'unmoderated_post'),
+            M.ACE.allow(role_auth, 'create'),
+            M.ACE.allow(role_developer, 'update'),
+            M.ACE.allow(role_developer, 'moderate'),
+            M.ACE.allow(role_developer, 'delete'),
+            M.ACE.allow(role_admin, 'configure'),
+            M.ACE.allow(role_admin, 'admin'),
+        ]
+
+    def uninstall(self, project):
+        """Remove all the tool's artifacts from the database"""
+        app_config_id = {'app_config_id': c.app.config._id}
+        TM.Feedback.query.remove(app_config_id)
+        super(ForgeFeedbackApp, self).uninstall(project)
+
+
+class RootController(BaseController, FeedController):
+
+    def _check_security(self):        
+        require_access(c.app, 'read')
+
+    @expose('jinja:forgefeedback:templates/feedback/index.html')
+    def index(self,**kw):        
+        require_access(c.app, 'read')        
+        user_has_already_reviewed = False
+        rating_by_user = Feedback.query.find({
+                'reported_by_id' : c.user._id,'project_id' : c.project._id}
+                 ).count() 
+        if (rating_by_user > 0) :
+             user_has_already_reviewed = True
+        return dict(review_list= self.get_review_list(), user_has_already_reviewed = user_has_already_reviewed, rating=self.getRating())
+   
+    # the list of all the feedbacks given by various users is listed on the index page      
+    @expose('jinja:forgefeedback:templates/feedback/index.html')
+    def get_review_list(self, **kw):      
+        self.review_list = Feedback.query.find(dict(project_id=c.project._id)).sort('created_date', pymongo.DESCENDING).all()
+        return self.review_list
+
+    # The new feedback given by the logged in user which includes the review, rating, project id and the user id are all flushed into the database
+    @require_post()
+    @expose('jinja:forgefeedback:templates/feedback/index.html')
+    def create_feedback(self, description=None, rating=None, **kw):
+        """saving the review for the first time """
+        require_access(c.app, 'create')
+        p = Feedback( description=description , rating=rating, user_id=c.user._id, project_id=c.project._id)
+        session(p).flush()
+        flash('Feedback successfully added')
+        M.main_orm_session.flush()
+	g.director.create_activity(c.user, 'posted', p,related_nodes=[c.project], tags=['description'])
+        return dict(review_list = self.get_review_list(), rating=self.getRating())
+    
+    # called on click of the Feedback link 
+    @with_trailing_slash
+    @expose('jinja:forgefeedback:templates/feedback/new_feedback.html')
+    def new_feedback(self, **kw):
+        require_access(c.app, 'create')
+        return dict(action=c.app.config.url() + 'create')
+       
+    #called on click of edit review link and displays the previous feedback 
+    @expose('jinja:forgefeedback:templates/feedback/edit_feedback.html')
+    def edit_feedback(self, **kw):
+        self.review = Feedback.query.find({'reported_by_id' : c.user._id,'project_id' : c.project._id}).first()     
+        return dict(description = self.review.description, rating=self.review.rating)
+    
+    # The edited feedback will be updated in the index page 
+    @expose('jinja:forgefeedback:templates/feedback/index.html')
+    def edit_user_review(self,description=None, rating=None, **kw):
+    
+            Feedback.query.update(
+                    {'reported_by_id': c.user._id,'project_id' : c.project._id},
+                    {'$set': {'description': description, 'rating':rating}})
+            self.rating = Feedback.query.find({'reported_by_id' : c.user._id,'project_id' : c.project._id}).first()
+            flash('Feedback successfully edited')
+            g.director.create_activity(c.user,'modified', self.rating, related_nodes=[c.project], tags=['description'])
+            return dict(review_list = self.get_review_list(), rating=self.getRating())
+
+    #called when user clicks on delete link in feedback page
+    @without_trailing_slash
+    @expose('jinja:forgefeedback:templates/feedback/index.html')
+    def delete_feedback(self, **kw):
+        user_review = Feedback.query.find({'reported_by_id' : c.user._id,'project_id' : c.project._id}).first()    
+        Feedback.query.remove(dict({'reported_by_id' : c.user._id,'project_id' : c.project._id}))
+        rating_by_user = Feedback.query.find({
+                'reported_by_id' : c.user._id,'project_id' : c.project._id}
+                ).count()
+        if (rating_by_user > 0) :
+             user_has_already_reviewed = True
+        else :
+             user_has_already_reviewed = False
+        flash('Feedback successfully deleted')
+        M.main_orm_session.flush()
+        return dict(review_list = self.get_review_list(), user_has_already_reviewed = user_has_already_reviewed, rating=self.getRating())
+    
+    #This method is used to check for profanity in feedback text
+    @expose()
+    def feedback_check(self, description=None):
+        pf = ProfanityFilter()
+        if description:
+            result = str(pf.is_profane(description)).lower()
+        else:
+            result = 'None'
+        return result
+
+
+    # This method count the number of stars finds their sum and calculates the average of all the star rating count    
+    def getRating(self, **kw):
+
+        
+        onestarcount = TM.Feedback.query.find({'rating':'1','project_id':c.project._id}).count()
+        twostarcount = TM.Feedback.query.find({'rating':'2','project_id':c.project._id}).count()
+        threestarcount = TM.Feedback.query.find({'rating':'3','project_id':c.project._id}).count()
+        fourstarcount = TM.Feedback.query.find({'rating':'4','project_id':c.project._id}).count()
+        fivestarcount = TM.Feedback.query.find({'rating':'5','project_id':c.project._id}).count()
+        sum_of_ratings = float(fivestarcount + fourstarcount + threestarcount + twostarcount + onestarcount)
+        if (sum_of_ratings != 0):
+            average_user_ratings =float((5*fivestarcount)+(4*fourstarcount)+(3*threestarcount)+(2*twostarcount)+(1*onestarcount))/sum_of_ratings
+            float_rating = float(average_user_ratings)
+            int_rating = int(float_rating)
+            float_point_value = float_rating - int_rating
+            if(float_point_value < 0.25 ):
+                c.project.rating= int_rating
+            elif(float_point_value>=0.25<0.75):
+                c.project.rating = 0.5 + int_rating
+            elif(float_point_value >= 0.75):
+                c.project.rating=float(int_rating)+1                  
+            return average_user_ratings
+        if (sum_of_ratings == 0):
+            c.project.rating = 0.0
+ 
+
+                               
+    
+
+
+
+
diff --git a/ForgeFeedback/forgefeedback/model/__init__.py b/ForgeFeedback/forgefeedback/model/__init__.py
new file mode 100644
index 0000000..19f031b
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/model/__init__.py
@@ -0,0 +1,21 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from feedback import Feedback
+
+
+
diff --git a/ForgeFeedback/forgefeedback/model/feedback.py b/ForgeFeedback/forgefeedback/model/feedback.py
new file mode 100644
index 0000000..2ef7326
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/model/feedback.py
@@ -0,0 +1,82 @@
+#	Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+import logging
+import urllib
+# Non-stdlib imports
+
+from datetime import datetime
+from bson import ObjectId
+from tg import tmpl_context as c
+from ming import schema
+from ming.orm import Mapper
+from ming.orm import FieldProperty, ForeignIdProperty, RelationProperty
+
+# Pyforge-specific imports
+
+from allura.model.artifact import VersionedArtifact
+from allura.model.auth import AlluraUserProperty, User
+from allura.model.project import ProjectRole 
+from allura.model.timeline import ActivityObject
+from allura.lib import helpers as h
+
+log = logging.getLogger(__name__)
+
+
+class Feedback(VersionedArtifact, ActivityObject):
+   
+    class __mongometa__:
+	name = 'feedback'
+   	
+    type_s = 'Feedback'
+    _id = FieldProperty(schema.ObjectId)
+    created_date = FieldProperty(datetime, if_missing=datetime.utcnow)
+    rating = FieldProperty(str, if_missing='')
+    description = FieldProperty(str, if_missing='')
+    reported_by_id = AlluraUserProperty(if_missing=lambda: c.user._id)
+    project_id = ForeignIdProperty('Project', if_missing=lambda: c.project._id)
+    reported_by = RelationProperty(User, via='reported_by_id')
+
+    def index(self):
+        result = VersionedArtifact.index(self)
+        result.update(
+            created_date_dt=self.created_date,
+            text=self.description,
+        )
+        return result 
+
+    @property
+    def activity_name(self):
+        return 'a review comment'
+
+    @property
+    def activity_extras(self):
+        d = ActivityObject.activity_extras.fget(self)
+        d.update(summary=self.description) 
+        return d
+    def url(self):
+      return self.app_config.url()
+
+Mapper.compile_all()
+
+
+
+
+
+
+
+
diff --git a/ForgeFeedback/forgefeedback/nf/feedback/css/feedback.css b/ForgeFeedback/forgefeedback/nf/feedback/css/feedback.css
new file mode 100644
index 0000000..15c2f9c
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/nf/feedback/css/feedback.css
@@ -0,0 +1,112 @@
+.rating {
+    float:left;
+}
+
+/* :not(:checked) is a filter, so that browsers that dont support :checked dont
+   follow these rules. Every browser that supports :checked also supports :not(), so
+   it doesnt make the test unnecessarily selective */
+.rating:not(:checked) > input {
+    position:absolute;
+    top:-9999px;
+    clip:rect(0,0,0,0);
+}
+.rating:not(:checked) > label {
+    float:right;
+    width:1em;
+    padding:0 .1em;
+    overflow:hidden;
+    white-space:nowrap;
+    cursor:pointer;
+    font-size:200%;
+    line-height:1.2;
+    color:#ddd;
+    text-shadow:1px 1px #bbb, 2px 2px #666, .1em .1em .2em rgba(0,0,0,.5);
+}
+.rating:not(:checked) > label:before {
+    content: '★ ';
+}
+.rating > input:checked ~ label {
+    color: #f70;
+    text-shadow:1px 1px #c60, 2px 2px #940, .1em .1em .2em rgba(0,0,0,.5);
+}
+
+.rating:not(:checked) > label:hover,
+.rating:not(:checked) > label:hover ~ label {
+    color: gold;
+    text-shadow:1px 1px goldenrod, 2px 2px #B57340, .1em .1em .2em rgba(0,0,0,.5);
+}
+.rating > input:checked + label:hover,
+.rating > input:checked + label:hover ~ label,
+.rating > input:checked ~ label:hover,
+.rating > input:checked ~ label:hover ~ label,
+.rating > label:hover ~ input:checked ~ label {
+    color: #ea0;
+    text-shadow:1px 1px goldenrod, 2px 2px #B57340, .1em .1em .2em rgba(0,0,0,.5);
+}
+.rating > label:active {
+    position:relative;
+    top:2px;
+    left:2px;
+}
+.feed {
+  padding: 0 20px 20px 20px;
+}
+.feed, .feed * {
+  box-sizing: border-box;
+}
+.feed form textarea {
+  width: 100%;
+  height: 200px;
+  display: block;
+}
+.index {
+  padding: 0 20px 20px 20px;
+}
+.index ul.list {
+  list-style: none;
+  margin: 0;
+}
+.index ul.list li {
+  padding: 20px;
+  border-bottom: 1px solid #eee;
+}
+.index ul.list li time {
+  font-size: 12px;
+  color: #777;
+}
+.index ul.list li h1 {
+  padding: 0;
+  font-size: 16px;
+  line-height: 32px;
+}
+.index ul.list li p {
+  padding: 0;
+  vertical-align: top;
+  font-size: 14px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.index ul.list li .avatar {
+  float: left;
+  margin: 10px 10px 0 0;
+}
+.fa-star {
+color:orange;
+}
+.fa-star-o {
+color :orange;
+}
+.rating {
+position:relative;
+}
+p {
+position:absolute;
+}
+.grid-19{
+  width:885px !important;
+}
+.btn.link.cancel_feed{
+  float:right;
+}
+
diff --git a/ForgeFeedback/forgefeedback/templates/__init__.py b/ForgeFeedback/forgefeedback/templates/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ForgeFeedback/forgefeedback/templates/feedback/common_feedback.html b/ForgeFeedback/forgefeedback/templates/feedback/common_feedback.html
new file mode 100644
index 0000000..48db583
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/templates/feedback/common_feedback.html
@@ -0,0 +1,87 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+This is used when user is trying to imput a new review 
+
+-#}
+
+
+<!-- macro for feedback textarea -->
+{% macro feed_textarea(name='description',id='description',placeholder='',description='') %}
+<textarea class="textbox" name="{{name}}" id="{{id}}" maxlength=100 onkeyup="manage()" placeholder="{{placeholder}}">{{description}}</textarea>
+{% endmacro %}
+
+<!-- macro for feedback alert message -->
+{% macro alert_message() %}
+	<div id="check">
+		<p id="status_msg" style="color:#f33;"> <i class="fa fa-exclamation-triangle"></i> Profanity alert! Please update your feedback.</p>
+	</div>
+{% endmacro %}
+
+<!-- macro for feedback cancel button -->
+{% macro feed_cancel(url='') %}
+    <a href="{{url}}" class="btn link cancel_feed" >Cancel</a>
+{% endmacro %}
+
+<!-- macro for profanity scripts -->
+{% macro profanity_scripts(url='') %}
+<script type="text/javascript">
+function manage()
+{
+    var description = document.getElementById('description');
+    var star5 = $('#star5').is(':checked');
+    var star4 = $('#star4').is(':checked');
+    var star3 = $('#star3').is(':checked');
+    var star2 = $('#star2').is(':checked');
+    var star1 = $('#star1').is(':checked');
+    var bt = document.getElementById('button');
+    if (description.value.trim().length != 0 && (star5 || star4 || star3 || star2 || star1 )) {
+        bt.disabled = false;
+    }
+    else {
+        bt.disabled = true;
+    }
+}
+
+</script>
+
+<script type="text/javascript">
+$('#status_msg').hide();
+
+$("#description").keyup(function(){
+	var description = $("#description").val();
+	$.ajax({
+		url:'{{url}}feedback_check',
+		type:'GET',
+		data: {'description':description},
+		success: function(status){
+			if (status == 'true'){
+				$('#button').attr('disabled', true);
+				$('#status_msg').show();
+			}
+			else{
+				$('#status_msg').hide();
+			}
+		}
+	});
+});
+</script>
+{% endmacro %}
+
+
+
+
diff --git a/ForgeFeedback/forgefeedback/templates/feedback/edit_feedback.html b/ForgeFeedback/forgefeedback/templates/feedback/edit_feedback.html
new file mode 100644
index 0000000..8f11e2a
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/templates/feedback/edit_feedback.html
@@ -0,0 +1,103 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+This is used when user is trying to imput a new review 
+
+-#}
+
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+{% import 'forgefeedback:templates/feedback/common_feedback.html' as common_feed %}
+
+{% extends g.theme.master %}
+{% set hide_left_bar = True %}
+{% do g.register_app_css('css/feedback.css') %}
+
+{% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Edit feedback {% endblock %}
+
+{% block header %}{{c.app.config.options.mount_label}}{% endblock %}
+
+{% block content %}
+<style>
+p {
+    position:inherit;
+}
+</style>
+
+<div class="feed">
+<form action="{{ c.app.url }}edit_user_review" method="post">
+
+<div class="row">
+<div class="col-25">
+
+	<p> <h2>Edit your feedback for <b> {{c.project.name}} </b></h2> </p>
+    {{ common_feed.alert_message() }}
+    
+	</div>
+        <div class="col-75">
+<fieldset class="rating">
+    {% if rating == '5' %}
+      <input type="radio" id="star5" name="rating" value="5" checked="checked" onclick="manage()" /><label for="star5" title="Excellent!"></label>
+    {% else %}
+      <input type="radio" id="star5" name="rating" value="5" onclick="manage()" /><label for="star5" title="Excellent!"></label>
+    {% endif %}
+    {% if rating == '4' %}
+      <input type="radio" id="star4" name="rating" value="4" checked="checked" onclick="manage()" /><label for="star4" title="Great!"></label>
+    {% else %}
+      <input type="radio" id="star4" name="rating" value="4" onclick="manage()" /><label for="star4" title="Great!"></label>
+    {% endif %}
+    {% if rating == '3' %}
+      <input type="radio" id="star3" name="rating" value="3" checked="checked" onclick="manage()" /><label for="star3" title="Good!"></label>
+    {% else %}
+      <input type="radio" id="star3" name="rating" value="3" onclick="manage()" /><label for="star3" title="Good!"></label>
+    {% endif %}
+    {% if rating == '2' %}
+      <input type="radio" id="star2" name="rating" value="2" checked="checked" onclick="manage()" /><label for="star2" title="Average"></label>
+    {% else %}
+      <input type="radio" id="star5" name="rating" value="5" onclick="manage()" /><label for="star5" title="Excellent!"></label>
+    {% endif %} 
+    {% if rating == '1' %}
+      <input type="radio" id="star1" name="rating" value="1" checked="checked" onclick="manage()" /><label for="star1" title="Poor"></label>
+    {% else %}
+      <input type="radio" id="star5" name="rating" value="5" onclick="manage()" /><label for="star5" title="Excellent!"></label>
+    {% endif %} 
+    
+</fieldset>
+</div>
+</div>
+
+<!-- feedback textarea's macro -->
+{{ common_feed.feed_textarea(placeholder='Edit your feedback here',description=description ) }}
+
+<div class="grid-19">
+   <input type="submit" value="Submit" id="button" >
+   {{ common_feed.feed_cancel(url=c.app.url) }}
+</div>
+
+{{ lib.csrf_token() }}
+
+</form>
+</div>
+
+{% endblock %}
+
+{% block extra_js %}
+<!-- profanity script's macro -->
+    {{ common_feed.profanity_scripts(url=c.app.url) }}
+{% endblock %}
+
+
diff --git a/ForgeFeedback/forgefeedback/templates/feedback/feedback_list.html b/ForgeFeedback/forgefeedback/templates/feedback/feedback_list.html
new file mode 100644
index 0000000..195fb2e
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/templates/feedback/feedback_list.html
@@ -0,0 +1,100 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% import 'forgeactivity:templates/macros.html' as am with context %}
+{#- to add the time -#}
+{% from 'allura:templates/jinja_master/lib.html' import abbr_date with context %}
+{% do g.register_app_css('css/feedback.css') %}
+{% block content %}
+
+{% for timeline in review_list %}
+<li>
+ <time>
+  {{abbr_date(timeline.created_date)}}
+ </time>
+<br/>
+ <h1>
+  {{ am.icon (timeline.reported_by, 32, 'avatar') }}
+  {{am.activity_obj(timeline.reported_by)}} 
+ </h1><br/>
+ <fieldset class="rating" style="float:left">
+   
+    {% if timeline.rating =='1' %}
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star-o "></span>
+      <span class="fa fa-star-o "></span>
+      <span class="fa fa-star-o "></span>
+      <span class="fa fa-star-o"></span> 
+  
+    {% endif %}
+  
+    {% if timeline.rating =='2' %}
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star-o"></span>
+      <span class="fa fa-star-o"></span>
+      <span class="fa fa-star-o"></span>
+  
+    {% endif %}
+  
+    {% if timeline.rating =='3' %}
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star-o"></span>
+      <span class="fa fa-star-o"></span>
+    {% endif %} 
+  
+    {% if timeline.rating =='4' %}
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span> 
+      <span class="fa fa-star-o"></span>
+  
+    {% endif %}
+  
+    {% if timeline.rating =='5' %}
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+      <span class="fa fa-star checked"></span>
+    {% endif %}
+ </fieldset>
+<br/>
+
+  {{timeline.description}}  
+
+</li>
+{% endfor %}
+{% endblock %}
+
+{% block extra_css %}
+<style>
+.rating {
+position:relative;
+}
+p {
+position:absolute;
+}
+</style>
+{% endblock %}
+
+
diff --git a/ForgeFeedback/forgefeedback/templates/feedback/index.html b/ForgeFeedback/forgefeedback/templates/feedback/index.html
new file mode 100644
index 0000000..fe96856
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/templates/feedback/index.html
@@ -0,0 +1,56 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+ e     Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+{% extends g.theme.master %}
+{% do g.register_app_css('css/feedback.css') %}
+{% set hide_left_bar = True %}
+{% block nav_menu %}
+{{super()}}
+<br/>
+<br/>
+{% endblock %}
+
+{% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Feedback{% endblock %}
+{% block actions %}
+{% endblock %}
+
+{% block header %}{{c.app.config.options.mount_label}}{% endblock %}
+
+{% block content %}
+     
+{% if c.user and c.user != c.user.anonymous() %} 
+<div class="index">
+	{% if user_has_already_reviewed == False %}   
+	<p><h2> Provide your <a href="{{c.app.url}}new_feedback">Feedback</a> for {{c.project.name}} </h2></p>
+	{% else %} 
+	<p><h2><a href="{{c.app.url}}edit_feedback"> Edit </a> or <a href="{{c.app.url}}delete_feedback"> Delete </a> your feedback for {{c.project.name}} </h2> </p>
+	{% endif %}  
+</div>
+{% else %}
+<p><h3> <a target="_parent" href="{{ config.get('auth.login_url', '/auth/') }}">Login </a> to Add/Modify Feedback</h3></p>
+{% endif %}
+<hr/>
+<div class ="index" >
+    <ul class="list">
+        {% include 'forgefeedback:templates/feedback/feedback_list.html' %}
+    </ul>  
+</div>
+{% endblock %}
+
+
+
diff --git a/ForgeFeedback/forgefeedback/templates/feedback/new_feedback.html b/ForgeFeedback/forgefeedback/templates/feedback/new_feedback.html
new file mode 100644
index 0000000..b472aab
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/templates/feedback/new_feedback.html
@@ -0,0 +1,79 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+This is used when user is trying to imput a new review 
+
+-#}
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+{% import 'forgefeedback:templates/feedback/common_feedback.html' as common_feed %}
+
+{% extends g.theme.master %}
+{% set hide_left_bar = True %} 
+{% do g.register_app_css('css/feedback.css') %}
+{% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / New feedback{% endblock %}
+
+{% block header %}{{c.app.config.options.mount_label}}{% endblock %}
+
+{% block content %}
+<style>
+p {
+    position:inherit;
+}
+</style>
+
+<div class="feed">
+    <form action="{{ c.app.url }}create_feedback" method="post">
+
+    <div class="row">
+        <div class="col-25">
+
+    <p> <h2>Provide your feedback for <b> {{c.project.name}}</b></h2> </p>
+    {{ common_feed.alert_message() }}
+    
+	</div>
+        <div class="col-75">
+          <fieldset class="rating" id="sar">
+              <input type="radio" id="star5" name="rating" value="5" onclick="manage()" /><label for="star5" title="Excellent">5 stars</label>
+              <input type="radio" id="star4" name="rating" value="4" onclick="manage()" /><label for="star4" title="Great">4 stars</label>
+              <input type="radio" id="star3" name="rating" value="3" onclick="manage()" /><label for="star3" title="Good">3 stars</label>
+              <input type="radio" id="star2" name="rating" value="2" onclick="manage()" /><label for="star2" title="Average">2 stars</label>
+              <input type="radio" id="star1" name="rating" value="1" onclick="manage()" /><label for="star1" title="Poor">1 star</label>
+          </fieldset>
+        </div>
+    </div>
+
+    <!-- feedback textarea's macro -->
+    {{ common_feed.feed_textarea(placeholder='Enter your feedback here',description=description ) }}
+    <div class="grid-19">
+       <input type="submit" value="Submit" id="button" disabled />
+       {{ common_feed.feed_cancel(url=c.app.url) }}
+    </div> 
+    {{ lib.csrf_token() }}
+    </form>
+</div>
+{% endblock %}
+
+
+{% block extra_js %}
+<!-- profanity script's macro -->
+    {{ common_feed.profanity_scripts(url=c.app.url) }}
+{% endblock %}
+
+
+
+
diff --git a/ForgeFeedback/forgefeedback/tests/__init__.py b/ForgeFeedback/forgefeedback/tests/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/__init__.py
@@ -0,0 +1,17 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
diff --git a/ForgeFeedback/forgefeedback/tests/functional/__init__.py b/ForgeFeedback/forgefeedback/tests/functional/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/functional/__init__.py
@@ -0,0 +1,17 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
diff --git a/ForgeFeedback/forgefeedback/tests/functional/test_root.py b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
new file mode 100644
index 0000000..b97eafa
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
@@ -0,0 +1,69 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+from tg import tmpl_context as c
+from tg import config
+
+from nose.tools import assert_equal, assert_in, assert_not_in, assert_true, assert_false, assert_raises
+
+from allura import model as M
+from alluratest.controller import TestController
+from allura.lib import helpers as h
+from allura.tests import decorators as td
+
+from forgefeedback import model as FM
+
+class TestFeedback(TestController):
+	def setUp(self):
+		TestController.setUp(self)
+	
+	def test_feedback(self):
+		c.user = M.User.by_username('test-admin')
+		self.app.get('/feedback/')
+		r = self.app.get('/p/test/feedback')
+		assert 'test' in r
+		assert_in('<a href="/p/test/feedback/new_feedback">Feedback</a>', r) 
+
+	def test_new_feedback(self):
+		c.user = M.User.by_username('test-admin')
+		self.app.get('/feedback/')
+		r = self.app.get('/p/test/feedback/new_feedback/')
+		assert_in('Provide your feedback for <b> Test Project</b>',r)
+		assert_in('Enter your feedback here', r)
+
+	def test_create_feedback(self):
+		resp = post_feedback(self)
+		assert_in('Good tool',resp)
+	
+	def test_edit_feedback(self):
+		post_feedback(self)
+		data = {'rating': '2','description':'Not useful'}
+		resp = self.app.post('/p/test/feedback/edit_user_review',data)
+		assert_in('Not useful',resp)
+
+	def test_delete_feedback(self):
+		post_feedback(self)
+		resp=self.app.get('/p/test/feedback/delete_feedback')
+		assert_in('<a href="/p/test/feedback/new_feedback">Feedback</a>',resp)
+		
+
+def post_feedback(self):
+	c.user =M.User.by_username('test-admin')
+        self.app.get('/feedback/')
+        self.app.get('/p/test/feedback')
+        data = {'rating':'4','description':'Good tool'}
+        resp = self.app.post('/p/test/feedback/create_feedback/',data)
+	return resp
diff --git a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
new file mode 100644
index 0000000..bd7f902
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
@@ -0,0 +1,54 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from tg import tmpl_context as c, app_globals as g
+
+from nose.tools import assert_equal
+
+from alluratest.controller import setup_basic_test, setup_global_objects
+from allura import model as M
+from allura.lib import security
+from allura.tests import decorators as td
+from allura.lib import helpers as h
+
+def setUp():
+    setup_basic_test()
+    setup_with_tools()
+
+
+@td.with_tool('u/test-user-1','feedback')
+@td.with_user_project('test-user-1')
+def setup_with_tools():
+    setup_global_objects()
+    h.set_context('test', neighborhood='Projects')
+    c.project.install_app('feedback', 'feedback')
+    g.set_app('feedback')
+
+
+def test_role_assignments():
+    admin = M.User.by_username('test-admin')
+    user = M.User.by_username('test-user')
+    anon = M.User.anonymous()
+
+    def check_access(perm):
+        pred = security.has_access(c.app, perm)
+        return pred(user=admin), pred(user=user), pred(user=anon)
+    assert_equal(check_access('read'), (True, True, True))
+    assert_equal(check_access('create'), (True, True, False))
+    assert_equal(check_access('update'), (True, False, False))
+    assert_equal(check_access('delete'), (True, False, False))
+
diff --git a/ForgeFeedback/forgefeedback/tests/unit/__init__.py b/ForgeFeedback/forgefeedback/tests/unit/__init__.py
new file mode 100644
index 0000000..9c6667a
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/unit/__init__.py
@@ -0,0 +1,51 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from tg import tmpl_context as c
+from ming.orm.ormsession import ThreadLocalORMSession
+
+from allura.websetup import bootstrap
+from allura.lib import helpers as h
+from allura.lib import plugin
+from allura import model as M
+from alluratest.controller import setup_basic_test
+
+
+def setUp():
+    setup_basic_test()
+
+
+class FeedbackTestWithModel(object):
+
+    def setUp(self):
+        bootstrap.wipe_database()
+        project_reg = plugin.ProjectRegistrationProvider.get()
+        c.user = bootstrap.create_user('Test User')
+        neighborhood = M.Neighborhood(name='Projects', url_prefix='/p/',
+                                      features=dict(private_projects=False,
+                                                    max_projects=None,
+                                                    css='none',
+                                                    google_analytics=False))
+        project_reg.register_neighborhood_project(neighborhood, [c.user])
+        c.project = neighborhood.register_project('test', c.user)
+        c.project.install_app('Feedback', 'feedback')
+        ThreadLocalORMSession.flush_all()
+        h.set_context('test', 'feedback', neighborhood='Projects')
+
+    def tearDown(self):
+        ThreadLocalORMSession.close_all()
+
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py b/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
new file mode 100644
index 0000000..604041e
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
@@ -0,0 +1,41 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from datetime import datetime
+from nose.tools import assert_equal, assert_true
+from tg import tmpl_context as c
+
+from forgefeedback.tests.unit import FeedbackTestWithModel
+from forgefeedback import model as M
+
+class TestFeedback(FeedbackTestWithModel):
+
+	def test_feedback(self):
+	   feedback = M.Feedback()
+	   feedback.rating='4'
+	   feedback.description='Very good tool'
+	   assert_equal(feedback.rating, '4')
+	   assert_equal(feedback.description, 'Very good tool')
+	   assert_equal(feedback.activity_extras['summary'], feedback.description)
+	   assert_true('allura_id' in feedback.activity_extras)
+ 
+	def test_index(self):
+	    feedback= M.Feedback()
+	    feedback.rating= '4'
+	    feedback.description ='Good tool'
+ 	    result= feedback.index()
+	    assert_equal(result["text"], 'Good tool')
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
new file mode 100644
index 0000000..f99332c
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
@@ -0,0 +1,80 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+import unittest
+
+from mock import Mock, patch
+from ming.orm.ormsession import session
+from tg import tmpl_context as c
+from tg import request
+from nose.tools import assert_equal
+
+from allura.lib import helpers as h
+from allura.model import User
+
+from forgefeedback.tests.unit import FeedbackTestWithModel
+from forgefeedback.model import Feedback
+from forgefeedback import feedback_main
+
+class TestFeedbackApp(FeedbackTestWithModel):
+
+    def setUp(self):
+        super(TestFeedbackApp, self).setUp()
+        c.user = User(username='test-user')
+        h.set_context('test', 'feedback', neighborhood='Projects')
+
+    def test_index(self):
+        reviews = feedback_main.RootController().index()
+        assert reviews['user_has_already_reviewed']==False
+        create_feedbacks()
+        reviews = feedback_main.RootController().index()
+        assert reviews['user_has_already_reviewed']==True
+        
+    def test_feedback(self):
+        create_feedbacks()
+        reviews = feedback_main.RootController().get_review_list()
+        assert reviews[0].description =='Very good tool'
+        assert reviews[1].description =='Not Useful'
+        assert reviews[0].rating =='5'
+        assert reviews[1].rating =='2'
+
+    def test_getRating(self):
+        create_feedbacks()
+        rating=feedback_main.RootController().getRating()
+        assert_equal(rating,3.5)
+
+    def test_edit_feedback(self):
+        create_feedbacks()
+        old_feedback= feedback_main.RootController().edit_feedback()
+        assert old_feedback['description'] =='Very good tool'
+
+    def test_check_feedback(self):
+        feed_check = feedback_main.RootController().feedback_check('good')
+        assert feed_check == 'false'
+        feed_check = feedback_main.RootController().feedback_check('shit')
+        assert feed_check == 'true'
+
+def create_feedbacks():
+		feedback_1= create_feedback('2', 'Not Useful')
+                c.user = User(username='test-admin')
+                h.set_context('test', 'feedback', neighborhood='Projects')
+                feedback_2 = create_feedback('5', 'Very good tool')
+
+def create_feedback(rating, description):
+                feedback = Feedback(rating=rating, description=description)
+                session(feedback).flush()
+                return feedback	
diff --git a/ForgeFeedback/forgefeedback/version.py b/ForgeFeedback/forgefeedback/version.py
new file mode 100644
index 0000000..1333217
--- /dev/null
+++ b/ForgeFeedback/forgefeedback/version.py
@@ -0,0 +1,24 @@
+#Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+__version_info__ = (0, 0)
+__version__ = '.'.join(map(str, __version_info__))
+
+
+
+
+
diff --git a/ForgeFeedback/setup.py b/ForgeFeedback/setup.py
new file mode 100644
index 0000000..cce91c5
--- /dev/null
+++ b/ForgeFeedback/setup.py
@@ -0,0 +1,53 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+
+from setuptools import setup, find_packages
+
+from forgefeedback.version import __version__
+
+setup(name='ForgeFeedback',
+      version=__version__,
+      description="",
+      long_description="""\
+""",
+      # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      classifiers=[],
+      keywords='',
+      author='',
+      author_email='',
+      url='',
+      license='',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+          'Allura',
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      [allura]
+      Feedback=forgefeedback.feedback_main:ForgeFeedbackApp
+
+      
+      """,
+      )
+
+
+
+
diff --git a/requirements.in b/requirements.in
index 35e0f5b..a8276a7 100644
--- a/requirements.in
+++ b/requirements.in
@@ -25,6 +25,8 @@ Paste
 PasteDeploy
 PasteScript
 Pillow
+# profanity filter for feedback
+profanityfilter==2.0.6
 Pygments
 pymongo==2.8.1
 Pypeline[creole,markdown,textile,rst]
@@ -57,4 +59,4 @@ testfixtures
 WebTest==2.0.33
 
 # deployment
-gunicorn==19.4.5
\ No newline at end of file
+gunicorn==19.4.5
diff --git a/requirements.txt b/requirements.txt
index 7d4c669..91b2080 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -40,6 +40,7 @@ gunicorn==19.4.5
 html5lib==1.0.1
 httplib2==0.13.1          # via oauth2
 idna==2.8                 # via requests
+inflection==0.3.1         # via profanityfilter
 ipaddress==1.0.22         # via cryptography
 ipython-genutils==0.2.0   # via traitlets
 ipython==5.8.0
@@ -64,6 +65,7 @@ pexpect==4.7.0            # via ipython
 pickleshare==0.7.5        # via ipython
 pillow==6.1.0
 poster==0.8.1
+profanityfilter==2.0.6
 prompt-toolkit==1.0.16    # via ipython
 ptyprocess==0.6.0         # via pexpect
 pycparser==2.19           # via cffi