diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f4f498 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +django_query_to_table/__pycache__/* +django_query_to_table.egg-info/* diff --git a/README.md b/README.md index 86446d7..afafa6e 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,14 @@ You can read more about this package here : [django query to table package](https://mshaeri.com/blog/generate-html-table-report-from-sql-query-in-django/) -The package contains one function named "generateFromSql" accepting 12 arguments : +The package contains two functions named: +- **generate_from_sql**: Generate HTML table by given SQL query +- **generate_from_queryset**:Generate HTML table by given Django queryset + +Parameters: -* cursor : DB cursor * title : The title of the report that will be shown on top of table -* sqltext : The sql select query to retrieve data +* sqltext/queryset : The sql select query to retrieve data / django queryset * footerCols : A list of columns name that you want to have Sum of values on footer . Example : ['amount','price'] * htmlClass : Html CSS classes for the table * direction (default = "ltr") : Indicates direction of the report page. "ltr"- Left to Right , "rtl" - Right to Left @@ -28,17 +31,16 @@ Run following command to install DjangoQtt : pip install django-query-to-table ``` -## Usage : +## Example usage : +- Generate HTML table by SQL query: ```python -from django.db import connection from django_query_to_table import DjangoQtt from django.http import HttpResponse # view function in Django project def listOfPersons(request): - cursor = connection.cursor() reportTitle = "Employee List" sqlQuery = "SELECT FirstName as 'First Name', LastName as 'Last Name', phone as 'Phone Number', salary as 'Salary' FROM persons" columnsToBeSummarized = ['Salary'] @@ -48,13 +50,40 @@ def listOfPersons(request): evenRowsBackgroundColor = '#ffeeff' oddRowsBackgroundColor = '#ffffff' rowIndexVisibility = True - table = DjangoQtt.generateFromSql(cursor, reportTitle, sqlQuery, columnsToBeSummarized, cssClasses, + table = DjangoQtt.generate_from_sql(reportTitle, sqlQuery, columnsToBeSummarized, cssClasses, "ltr", fontName, "Total Salary", rowIndexVisibility, headerRowBackgroundColor, evenRowsBackgroundColor, oddRowsBackgroundColor ) - # here the table is a string variable contianing the html table showing the query result + # here the table is a string variable containing the html table showing the query result return HttpResponse(table) ``` +- Generate HTML table from querset: + +```python +from django_query_to_table import DjangoQtt +from django.http import HttpResponse +from .models import Order +# view function in Django project +def listOfPersons(request): + + order_queryset = Order.objects.all().values("customer", "product", "amount") + reportTitle = "Order List" + columnsToBeSummarized = ['amount'] + fontName = "Arial" + cssClasses = "reportTable container" + headerRowBackgroundColor = '#ffeeee' + evenRowsBackgroundColor = '#ffeeff' + oddRowsBackgroundColor = '#ffffff' + rowIndexVisibility = True + table = DjangoQtt.generate_from_queryset(reportTitle, order_queryset, columnsToBeSummarized, cssClasses, + "ltr", fontName, "Total amount", rowIndexVisibility, + headerRowBackgroundColor, evenRowsBackgroundColor, oddRowsBackgroundColor + ) + + # here the table is a string variable containing the html table showing the queryset result + return HttpResponse(table) + + ``` \ No newline at end of file diff --git a/django_query_to_table/DjangoQtt.py b/django_query_to_table/DjangoQtt.py index 3aae7d9..4036357 100644 --- a/django_query_to_table/DjangoQtt.py +++ b/django_query_to_table/DjangoQtt.py @@ -1,80 +1,114 @@ +import traceback +import warnings from django.template import Template, Context +from django.db import connection +import logging -import traceback +logger = logging.getLogger(__name__) + +def _validate_sql(sqltext): + """Validate the SQL query.""" + if len(sqltext) < 8 or ("select" not in sqltext.lower()): + return False + return True + +def _execute_query(cursor, sqltext): + """Execute the SQL query and fetch data.""" + cursor.execute(sqltext) + desc = cursor.description + result_as_list = [ + dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall() + ] + columns = [col[0] for col in desc] + return columns, result_as_list + +def _calculate_sums(data, columns, footerCols, totalText): + """Calculate sum for specified columns.""" + sumOfColumn = {col: 0 if col in footerCols else "-" for col in columns} + + for d in data: + for attr, value in d.items(): + if attr in footerCols: + sumOfColumn[attr] += float(str(value).replace(",", "")) + + totalColumnSet = False + for col in sumOfColumn: + if sumOfColumn[col] != "-": + sumOfColumn[col] = format(float(str(sumOfColumn[col])), ",") + elif not totalColumnSet: + sumOfColumn[col] = totalText + totalColumnSet = True + + return sumOfColumn + +def _render_template(context): + """Render the HTML table template.""" + template = ( + "
" + " " + " {% if rowIndex %} {% endif %} {% for c in columns %} {% endfor %} " + " {% for d in data %} " + " {% if rowIndex %} {% endif %} {% for attr, value in d.items %} {% endfor %} {% endfor %} " + " {% if sumOfColumn %} {% for a,v in sumOfColumn.items %} {% endfor %} {% endif %}
{{title}}
# {{ c }}
{{ forloop.counter }}{{ value }}
{{ v }}
" + ) + + return Template(template).render(Context(context)) + +def _generate_report(cursor, title, sqltext, footerCols, htmlClass, direction, font, totalText, rowIndex, headerRowColor, evenRowColor, oddRowColor): + """Internal method to generate the SQL report.""" + columns, data = _execute_query(cursor, sqltext) + + sumOfColumn = None + if footerCols: + sumOfColumn = _calculate_sums(data, columns, footerCols, totalText) + + context = { + 'title': title, + 'data': data, + 'columns': columns, + 'sumOfColumn': sumOfColumn, + 'direction': direction, + 'font': font, + 'totalText': totalText, + 'rowIndex': rowIndex, + 'headerRowColor': headerRowColor, + 'evenRowColor': evenRowColor, + 'oddRowColor': oddRowColor, + 'htmlClass': htmlClass + } + + return _render_template(context) + +def generate_from_sql(title, sqltext, footerCols=None, htmlClass="", direction="ltr", font="Tahoma", totalText="Total", rowIndex=False, headerRowColor='#eeeeee', evenRowColor='#ffffff', oddRowColor="#ffffff"): + """Generate the SQL report with an internally created cursor.""" + try: + if not _validate_sql(sqltext): + return 'Not Valid SQL' + + with connection.cursor() as cursor: + return _generate_report(cursor, title, sqltext, footerCols, htmlClass, direction, font, totalText, rowIndex, headerRowColor, evenRowColor, oddRowColor) + except Exception as e: + logger.error(traceback.format_exc()) + return f"Error: {str(e)}" + +def generateFromSql(cursor, title, sqltext, footerCols=None, htmlClass="", direction="ltr", font="Tahoma", totalText="Total", rowIndex=False, headerRowColor='#eeeeee', evenRowColor='#ffffff', oddRowColor="#ffffff"): + """Generate the SQL report with a provided cursor.""" + warnings.warn("generateFromSql is deprecated and will be removed in a future release. Use generate_from_sql instead.", DeprecationWarning) + try: + if not _validate_sql(sqltext): + return 'Not Valid SQL' -def generateFromSql(cursor, title, sqltext, footerCols= None, htmlClass="", direction="ltr", font="Tahoma", totalText = "Total", rowIndex = False, headerRowColor ='#eeeeee' ,evenRowColor = '#ffffff', oddRowColor="#ffffff") : - sumCols=[] - try : - if(len(sqltext) < 8 or ("select" not in sqltext.lower())) : - return ('Not Valid SQL') - - sql_query = sqltext - sumCols=footerCols - # execute sql query and retrieve data from db - cursor.execute(sql_query) - - # retrieve columns of the data - desc = cursor.description - - result_as_list = [ - dict(zip([col[0] for col in desc ], row)) for row in cursor.fetchall() - ] - - columns = [col[0] for col in desc ] #result.keys() - data = result_as_list - - - - - - sumOfColumn = None - if(sumCols != None) : - sumOfColumn={} - - # initiating footer aggrigation values - for c in columns : - if(c in sumCols): - sumOfColumn[c]=0 - else: - sumOfColumn[c]="-" - - # travers in rows and aggrigate the values of columns those are in footerCols - for d in data : - for attr, value in dict(d).items() : - if(attr in sumCols): - sumOfColumn[attr]=sumOfColumn[attr]+float(str(value).replace(",", "")) - - totalColumnSet = False - if(sumCols != None) : - for col, val in sumOfColumn.items() : - if(val!="-") : - sumOfColumn[col] = format(float(str(val)),",") - elif (totalColumnSet == False) : - sumOfColumn[col] = totalText - totalColumnSet = True - - # template to generate data from data retrieved from database - template= "
{% if rowIndex == True %} {% endif %} {% for c in columns %} {% endfor %} {% for d in data %} {% if rowIndex == True %} {% endif %} {% for attr, value in d.items %} {% endfor %} {% endfor %} {% if sumOfColumn != None %} {% for a,v in sumOfColumn.items %} {% endfor %} {% endif %}
{{title}}
# {{ c }}
{{ forloop.counter }}{{ value }}
{{ v }}
" - - - - c = Context({ - 'title':title, - 'data':data, - 'columns':columns, - 'sumOfColumn':sumOfColumn, - 'direction':direction, - 'font':font, - 'totalText':totalText, - 'rowIndex' : rowIndex, - 'headerRowColor' :headerRowColor , - 'evenRowColor' : evenRowColor, - 'oddRowColor': oddRowColor, - 'htmlClass' : htmlClass - }) - - return Template(template).render(c) - except BaseException as e : - #print exception trace to console - print(traceback.format_exc()) - return ("Error :" + str(e)) + return _generate_report(cursor, title, sqltext, footerCols, htmlClass, direction, font, totalText, rowIndex, headerRowColor, evenRowColor, oddRowColor) + except Exception as e: + logger.error(traceback.format_exc()) + return f"Error: {str(e)}" + +def generate_from_queryset(title, queryset, footerCols=None, htmlClass="", direction="ltr", font="Tahoma", totalText="Total", rowIndex=False, headerRowColor='#eeeeee', evenRowColor='#ffffff', oddRowColor="#ffffff"): + """Generate the SQL report using a Django QuerySet.""" + try: + sqltext = str(queryset.query) + return generate_from_sql(title, sqltext, footerCols, htmlClass, direction, font, totalText, rowIndex, headerRowColor, evenRowColor, oddRowColor) + except Exception as e: + logger.error(traceback.format_exc()) + return f"Error: {str(e)}" \ No newline at end of file diff --git a/setup.py b/setup.py index b059bdc..b3ace15 100644 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ setup_args = dict( name='django-query-to-table', - version='0.1.9', - description='A simple to use Django package to turn your sql query into a beautiful reporting html table', + version='1.0.0', + description='A simple to use Django package to turn your queryset and sql query into a beautiful reporting html table', long_description_content_type="text/markdown", long_description=README, license='GNU',