Parsing recursive templates with pyparsing - python

I'm currently trying to parse recursive templates with pyparsing. A template can look like this:
{{Attribute
| name=attr1
| description=First attribute.}}
The template has a name (Attribute) and defines some variables (name = attr1, description = First attribute.). However, there are also templates which can contain zero or more templates:
{{Enum
| name=MyEnum
| description=Just a test enum.
| example=Not given...
| attributes={{Attribute
| name=attr1
| description=First attribute.}}
{{Attribute
| name=attr2
| description=Second attribute.}}}}
To parse these templates I came up with the following:
template = Forward()
lb = '{{'
rb = '}}'
template_name = Word(alphas)
variable = Word(alphas)
value = CharsNotIn('|{}=') | Group(ZeroOrMore(template))
member = Group(Suppress('|') + variable + Suppress('=') + value)
members = Group(OneOrMore(member))
template << Suppress(lb) + Group(template_name + members) + Suppress(rb)
This works quite well, but it does not allow me to use "|{}=" within a value which is problematic if I want to use them. E.g.:
{{Enum
| name=MyEnum
| description=Just a test enum.
| example=<python>x = 1</python>
| attributes=}}
So, how can I change my code so that it allows these characters, too? Unforunately, I have no idea how I can archieve this.
I hope someone can give me some tips!

I found what I was looking for: https://github.com/earwig/mwparserfromhell

Related

add prefix to id in template if flag is set in django

My database was something like this :
| id | customer_account | -few more fields - | is_renewed |
| 25 | asd111 | - some values - | 0 |
| 26 | asd222 | - some values - | 1 |
| 27 | asd333 | - some values - | 1 |
| 28 | asd444 | - some values - | 0 |
in my models, I have :
class Policy(models.Model):
customer_account = models.ForeignKey(CustomerAccount, on_delete=models.CASCADE)
--few more fields--
is_renewed = models.BooleanField(default = False)
def use_updated_id(self):
if self.is_renewed:
new_id = str("R") + str(self.id)
else:
new_id = self.id
return new_id
in my template, I have :
{% for policy in policy_list % }
<p> Policy Number : {{policy.id}} </p>
{% endfor %}
which gives me output as
Policy Number : 25
Policy Number : 26
Policy Number : 27
Policy Number : 28
I understand that I can define a method in model and use that instead of id as below to meet my requirement
{% for policy in policy_list % }
<p> Policy Number : {{policy.use_updated_id}} </p>
{% endfor %}
Policy Number : 25
Policy Number : R26
Policy Number : R27
Policy Number : 28
My only challenge is that if use model method as above, i will have to replace updated multiple templates.
I'm looking for a better solution where in i only have to make changes in models file instead of updating multiple templates to achieve the desired result.
So you have {{ policy.id }} in multiple templates and want to change its behavior by making changes to models.py?
AFAIK you cannot achieve that, since you haven't correctly encapsulated the display beforehand. That's a pain, but you'll have to change it everywhere, since you're accessing a particular attribute on your models. Adding your use_updated_id is a great idea, since it encapsulates the display logic in one function and, in the future, if you need to change the display all you have to do is to change your new function.
So go on, make those hundreds of file edits but be sure that now you've made a great progress and facilitated your project maintainability.

How to concat two columns of table django model

I am implementing search in my project what I want is to concat to column in where clause to get results from table.
Here is what I am doing:
from django.db.models import Q
if 'search[value]' in request.POST and len(request.POST['search[value]']) >= 3:
search_value = request.POST['search[value]'].strip()
q.extend([
Q(id__icontains=request.POST['search[value]']) |
(Q(created_by__first_name=request.POST['search[value]']) & Q(created_for=None)) |
Q(created_for__first_name=request.POST['search[value]']) |
(Q(created_by__last_name=request.POST['search[value]']) & Q(created_for=None)) |
Q(created_for__last_name=request.POST['search[value]']) |
(Q(created_by__email__icontains=search_value) & Q(created_for=None)) |
Q(created_for__email__icontains=search_value) |
Q(ticket_category=request.POST['search[value]']) |
Q(status__icontains=request.POST['search[value]']) |
Q(issue_type__icontains=request.POST['search[value]']) |
Q(title__icontains=request.POST['search[value]']) |
Q(assigned_to__first_name__icontains=request.POST['search[value]']) |
])
Now I want to add another OR condition like:
CONCAT(' ', created_by__first_name, created_by__last_name) like '%'search_value'%'
But when I add this condition to the queryset it becomes AND
where = ["CONCAT_WS(' ', profiles_userprofile.first_name, profiles_userprofile.last_name) like '"+request.POST['search[value]']+"' "]
tickets = Ticket.objects.get_active(u, page_type).filter(*q).extra(where=where).exclude(*exq).order_by(*order_dash)[cur:cur_length]
How do I convert this into an OR condition?
Advanced filters can be solved by
Q() object and
Query expressions like Func(), Value() and F().
The only used trick is a
Custom Lookup "rhs_only" that uses the right-hand-side of the lookup and ignores the left side, because it is easier to use all concatenated fields directly on the right side. A memorable function concat_like encapsulates that all to be easily used in queries.
from django.db.models import F, Func, Lookup, Q, Value
from django.db.models.fields import Field
def concat_like(columns, pattern):
"""Lookup filter: CONCAT_WS(' ', column_0, column_1...) LIKE pattern"""
lhs = '%s__rhs_only' % columns[0]
expr = Func(*(F(x) for x in columns), template="CONCAT_WS(' ', %(expressions)s)")
return Q(**{lhs: Like(expr, Value(pattern))})
class Like(Func):
def as_sql(self, compiler, connection):
arg_sql, arg_params = zip(*[compiler.compile(x) for x in self.source_expressions])
return ("%s LIKE '%s'" % tuple(arg_sql)), arg_params[0] + arg_params[1]
#Field.register_lookup
class RhsOnly(Lookup):
"""Skip the LHS and evaluate the boolean RHS only"""
lookup_name = 'rhs_only'
def as_sql(self, compiler, connection):
return self.process_rhs(compiler, connection)
All boolean expression and related objects are supported by this code. All arguments are correctly escaped.
Example usage:
>>> qs = MyModel.objects.filter(Q(id=1) | concat_like(('first_name', 'surname'), 'searched'))
>>> str(qs.query) # sql output simplified here
"SELECT .. WHERE id=1 OR (CONCAT_WS(' ', first_name, surname) LIKE 'searched')"
Relevant documentation:
https://docs.djangoproject.com/en/1.11/ref/models/expressions/#func-expressions
See also... Value() and F()
https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#aggregations-and-other-queryset-clauses
You can reference annotated fields from inside the filter method. As such, you can filter against two concatenated fields and add it as another OR condition like this:
from django.db.models import F, Func, Value
# Because we added user_full_name as an annotation below,
# we can refer to it in the filters
q.extend([
Q(id__icontains=request.POST['search[value]']) |
(Q(created_by__first_name=request.POST['search[value]']) & Q(created_for=None)) |
Q(created_for__first_name=request.POST['search[value]']) |
(Q(created_by__last_name=request.POST['search[value]']) & Q(created_for=None)) |
Q(created_for__last_name=request.POST['search[value]']) |
(Q(created_by__email__icontains=search_value) & Q(created_for=None)) |
Q(created_for__email__icontains=search_value) |
Q(ticket_category=request.POST['search[value]']) |
Q(status__icontains=request.POST['search[value]']) |
Q(issue_type__icontains=request.POST['search[value]']) |
Q(title__icontains=request.POST['search[value]']) |
Q(assigned_to__first_name__icontains=request.POST['search[value]']) |
Q(user_full_name__icontains=request.POST['search[value]']) # <------
])
# Add the annotation to your queryset
# I'm not actually sure what the related_name or field_name for your user
# profiles are, so I'm pretending that tickets have a profile foreignkey field
# to where the first_name and last_name fields are
user_full_name_expr = Func(Value(' '), F('profile__first_name'), F('profile__last_name'), function='CONCAT_WS')
# The next two lines can be combined as long as the annotation comes first.
tickets = Ticket.objects.annotate(user_full_name=user_full_name_expr)
tickets = tickets.get_active(u, page_type).filter(*q).exclude(*exq).order_by(*order_dash)[cur:cur_length]
For fun, here's a working example based on the User model.
from django.contrib.auth.models import User
from django.db.models import F, Func, Value
User.objects.create(username='john', first_name='John', last_name='Jingleheimer-Schmidt')
User.objects.create(username='mike', first_name='Michael', last_name='Finnigan')
foo = User.objects.annotate(full_name=Func(Value(' '), F('first_name'), F('last_name'), function='CONCAT_WS'))
print(foo.filter(full_name__icontains='john'))
# outputs: [<User: john>]
What you need is, for create search FullText. I recommend use (http://haystacksearch.org/)
See documentation of Django (https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/search/)

Group my-sql column objects of a model having same data on Django admin side

I have a model with the following definition
class exam_questions(models.Model):
exam_name=models.ForeignKey(exam,on_delete=models.CASCADE)
question=models.ForeignKey(questions,on_delete=models.CASCADE)
class Meta:
db_table = 'examquestions'
unique_together = (("exam_name", "question"),)
def __str__(self):
return '%s - %s' % (self.exam_name, self.question)
The data on sql table will look like this
+----+----------------+-------------+
| id | exam_name | question |
+----+----------------+-------------+
| 2 | test2 | 29 |
| 3 | test1 | 41 |
| 6 | test2 | 40 |
| 7 | test1 | 42 |
+----+----------------+-------------+
On Django admin I am looking the model objects like the following:
test2-29
test1-41
test2-40
test1-42
Now I want to group questions of same test and want to look them like the below:
test2-29,40
test1-41,42
I tried using normal python string operations, none of them worked on amdin django instead gave me errors.
Is there way for doing this. Any help is greatly appreciated.
Thanks
You can override objects manager in this way :-
class ExamManager(models.Manager):
def get_queryset(self):
return super(ExamManager,self).get_queryset().group_by('exam_name')
class exam_questions(models.Model):
exam_name=models.ForeignKey(exam,on_delete=models.CASCADE)
question=models.ForeignKey(questions,on_delete=models.CASCADE)
objects = ExamManager()

Django aggregation across multiple tables in ModelAdmin queryset

Django Code & Reference to Django Bug Report
Given three models as follows (simplified excessively for demonstration...not actually identical related models)
class derp(models.Model):
...
class derp_related_1(models.Model):
fk = models.ForeignKey(derp)
amount = models.DecimalField(max_digits=15, decimal_places=2)
class derp_related_2(models.Model):
fk = models.ForeignKey(derp)
amount = models.DecimalField(max_digits=15, decimal_places=2)
And overriding a queryset in the model admin as follows. (It isn't working because of this django bug.)
class DerpAdmin(admin.ModelAdmin):
...
list_display = ['derp_r1_sum', 'derp_r2_sum']
...
def queryset(self, request):
qs = super(DerpAdmin, self).queryset(request)
qs = qs.annotate(derp_r1_sum=models.Sum('derp_r1__amount', distinct=True))
qs = qs.annotate(derp_r2_sum=models.Sum('derp_r2__amount', distinct=True))
def derp_r1_sum(self, obj):
return u'%s' % obj.derp_r1_sum
def derp_r2_sum(self, obj):
return u'%s' % obj.derp_r2_sum
Example of Unexpected Database Result
Running annotations individually would render something like (with grouping & sums removed)
+---------+--------+
| derp.id | r1_sum |
+---------+--------+
| 2 | 500.00 |
| 2 | 100.00 |
+---------+--------+
r1_sum would be 600.00
and
+---------+--------+
| derp.id | r1_sum |
+---------+--------+
| 2 | 100.00 |
| 2 | 250.00 |
+---------+--------+
r2_sum would be 350.00
If you take qs.query with both annotations included and remove the sums and the grouping it is obvious what the problem is. In this case we're counting everything twice. Get more relations and we have an increasingly ugly increase in both sum columns.
+---------+--------+--------+
| derp.id | r1_sum | r2_sum |
+---------+--------+--------+
| 2 | 500.00 | 100.00 |
| 2 | 500.00 | 250.00 |
| 2 | 100.00 | 100.00 |
| 2 | 100.00 | 250.00 |
+---------+--------+--------+
r1_sum would incorrectly be 1200.00
r2_sum would incorrectly be 700.00
Question, is there a route other than custom SQL?
I can write the query myself easy enough, but if anyone has a suggestion which would avoid the writing of custom SQL that would be awesome.
Thanks for the help.
Edit: Here is a link to the annotations section of the Django documentation. One commenter mentioned the distinct option. This does not work, and I believe it is what is warned about at the bottom of the annotation section in the django documentation on annotation.
Edit2: The raw SQL idea likely is more difficult than I thought as derp.objects.raw('sql here') does not return the queryset object necessary for the admin to use it. Is there a way to use two queries (the real queryset plus a custom one doing the sums) and populate the listview from both? One suggestion I found (which I cannot find again now :S) suggested creating a view that maps to a Model definition which is then set to unmanaged by django (for syncdb). I could then write my custom code, and reference it for inclusion in the original query. This sounds messy. Thoughts?
If you want to stay within Django's queryset, I would consider creating a model superclass that shares the related and common fields and sub-classing for further distinctions. Otherwise, you need to either write custom SQL or get out of the database ORM entirely and manipulate your data in python with Queryset.values or Queryset.values_list
The best way I found to return the correct results was by using queryset.extra().
derp_r1_sum_select = """
select sum(`derp_related_1`.`amount`)
from `derp_related_1`
where `derp_related_1`.`fk` = `derp`.`pk`
"""
derp_r2_sum_select = """
select sum(`derp_related_2`.`amount`)
from `derp_related_2`
where `derp_related_2`.`fk` = `derp`.`pk`"
"""
def queryset(self, request):
qs = super(DerpAdmin, self).queryset(request)
qs = qs.extra(select={'derp_r1_sum': derp_r1_sum_select,
'derp_r2_sum': derp_r2_sum_select})
return qs

django make query

DB TABLE
select * from AAA;
id | Name | Class | Grade |
--------------------------------------
1 | john | 1 | A |
2 | Jane | 2 | B |
3 | Joon | 2 | A |
4 | Josh | 3 | C |
|
Code
Django
search_result = AAA.objects.filter(Grade = 'B').count()
print search_result
search_result -> 2
I want to change Grade to Class by VALUE.
Django
target_filter = 'Class'
search_result = AAA.objects.filter(__"target_filter..."__ = '3').count()
search_result -> 1
Q) How can I complete this code? Is it possible?
Maybe you can do it like this:
target_filter = 'Class'
filter_args = {target_filter: 3}
search_result = AAA.objects.filter(**filter_args).count()
you can shorten aeby's example by putting the kwargs in directly
target_filter = 'Class'
search_result = AAA.objects.filter(**{target_filter:3}).count()
It is somehow the same answer I gave here. You can also use the getattr but now with the module object itself. This is either __module__ if it is the same module, or the module you imported.
target_filter = 'Class'
search_result = AAA.objects.filter(getattr(__module__, target_filter) = '3').count()
EDIT: I got it wrong, it is not possible to access the current module via __module__. If the class is declared in the same module as your search, you can use globals()[target_filter] to access it. If your search is from another module, you can do it like this:
import somemodule
...
target_filter = 'Class'
search_result = AAA.objects.filter(getattr(somemodule, target_filter) = '3').count()

Categories