Repository: LMFDB/lmfdb

Issue:
Fix bread crumb for L-functions associated to Elliptic curves over number fields
At the moment the `Elliptic Curve` in the bread crumb 
```
 ⌂ → L-functions → Degree 4 → Elliptic curve → 2.2.5.1.31.1.a
```
points to http://beta.lmfdb.org/L/degree2/EllipticCurve/.
Which is clearly wrong :)

There is no http://beta.lmfdb.org/L/degree4/EllipticCurve/, but I am also not sure if we really want to have a page per degree....



Discussion:
One suggestion:  instead of having any degree-specific pages /L/degree<d>/EllipticCurve, just have one /L/EllipticCurve.  That could have a general opening paragraph, a more detailed section for degree 2 as now, and possibly more to be added later (if the curves over sextic fields get L-functions one day they will have degree 12).

This would however go against the other scheme we have whereby /L/degree<d> is a page linking to all known sources of degree d L-functions.  So maybe we do need a page per degree (of e.c. L-fns) after all.
Alternatively, one could entirely skip that crumb, as it is done in the genus 2 curves:
http://beta.lmfdb.org/L/Genus2Curve/Q/791597/a/

Resolving patch:
diff --git a/lmfdb/lfunctions/Lfunction.py b/lmfdb/lfunctions/Lfunction.py
--- a/lmfdb/lfunctions/Lfunction.py
+++ b/lmfdb/lfunctions/Lfunction.py
@@ -11,10 +11,12 @@
 
 from flask import url_for
 
-from Lfunctionutilities import (p2sage, string2number,
+from Lfunctionutilities import (p2sage, string2number, get_bread,
                                 compute_local_roots_SMF2_scalar_valued,
-                                signOfEmfLfunction)
-from LfunctionComp import EC_from_modform #modform_from_EC
+                                signOfEmfLfunction,
+                                name_and_object_from_url)
+from LfunctionComp import EC_from_modform, isogeny_class_cm
+
 import LfunctionDatabase
 import LfunctionLcalc
 from Lfunction_base import Lfunction
@@ -464,18 +466,63 @@ def __init__(self, **kwargs):
         self.__dict__.update(kwargs)
         self.lfunc_data = LfunctionDatabase.get_lfunction_by_Lhash(self.Lhash)
         makeLfromdata(self)
-        self.htmlname_arithmetic = "<em>L</em>(<em>s</em>)"
-        self.texname = "L(s)"
-        self.texname_arithmetic = "L(s)"
-        self.texnamecompleted1ms = "\\Lambda(1-s)"
-        self.texnamecompleteds_arithmetic = "\\Lambda(s)"
-        self.texnamecompleted1ms_arithmetic = "\\Lambda(" + str(self.motivic_weight + 1) + "-s)"
-        self.texnamecompleteds = "\\Lambda(s)"
+        self._set_web_displaynames()
         self.info = self.general_webpagedata()
         self._set_title()
         self.credit = ''
         self.label = ''
 
+    @property
+    def bread(self):
+        return [('L-functions', url_for('.l_function_top_page'))]
+
+    @property
+    def factors(self):
+        lfactors = []
+        if "," in self.Lhash:
+            for factor_Lhash in  self.Lhash.split(","):
+                for instance in sorted(LfunctionDatabase
+                                       .get_instances_by_Lhash(factor_Lhash),
+                                       key=lambda elt: elt['url']):
+                    url = instance['url']
+                    name, obj_exists = name_and_object_from_url(url)
+                    if obj_exists:
+                        lfactors.append((name,  "/" + url))
+                    else:
+                        name += '&nbsp;  n/a';
+                        lfactors.append((name, ""))
+        return lfactors
+
+    @property
+    def instances(self):
+        linstances = []
+        for instance in sorted(LfunctionDatabase
+                               .get_instances_by_Lhash(self.Lhash),
+                               key=lambda elt: elt['url']):
+            url = instance['url']
+            linstances.append((str(url), "/L/" + url))
+        return linstances
+
+    @property
+    def origins(self):
+        lorigins = []
+        for instance in sorted(LfunctionDatabase
+                               .get_instances_by_Lhash(self.Lhash),
+                               key=lambda elt: elt['url']):
+            name, obj_exists = name_and_object_from_url(instance['url'])
+            if not name:
+                name = ''
+            if obj_exists:
+                lorigins.append((name, "/"+instance['url']))
+            else:
+                name += '&nbsp;  n/a';
+                lorigins.append((name, ""))
+        return lorigins
+
+    @property
+    def friends(self):
+        return []
+
     def _set_title(self):
         '''
         If `charactermodulus` and `characternumber` are defined, make a title
@@ -498,59 +545,48 @@ def _set_title(self):
                     " and conductor {conductor}"
                     ).format(degree=self.degree, weight=self.motivic_weight,
                             conductor=self.level)
-
         self.info['title_arithmetic'] = ("L-function" + title_end)
         self.info['title_analytic'] = ("L-function" + title_end)
+        return
 
-
+    def _set_web_displaynames(self):
+        self.htmlname_arithmetic = "<em>L</em>(<em>s</em>)"
+        self.texname = "L(s)"
+        self.texname_arithmetic = "L(s)"
+        self.texnamecompleted1ms = "\\Lambda(1-s)"
+        self.texnamecompleteds_arithmetic = "\\Lambda(s)"
+        self.texnamecompleted1ms_arithmetic = "\\Lambda(" + str(self.motivic_weight + 1) + "-s)"
+        self.texnamecompleteds = "\\Lambda(s)"
+        return
 
 
 #############################################################################
 
 
 class Lfunction_EC(Lfunction):
-    """Class representing an elliptic curve L-function
-     over a number field, possibly QQ.
+    """
+    Class representing an elliptic curve L-function
+    over a number field, possibly QQ.
     It should be called with a dictionary of the forms:
 
-    dict = { 'field_label': <field_label>, 'conductor_label': <conductor_label>, 'isogeny_class_label': <isogeny_class_label> }
+    dict = { 'field_label': <field_label>, 'conductor_label': <conductor_label>,
+             'isogeny_class_label': <isogeny_class_label> }
     """
-    def __init__(self, **args):
-        constructor_logger(self, args)
+    def __init__(self, **kwargs):
+        constructor_logger(self, kwargs)
         validate_required_args('Unable to construct elliptic curve L-function.',
-                               args, 'field_label', 'conductor_label', 'isogeny_class_label')
-
+                               kwargs, 'field_label', 'conductor_label',
+                               'isogeny_class_label')
         self._Ltype = "ellipticcurve"
-
-
-        # Put the arguments into the object dictionary
-        self.__dict__.update(args)
         self.numcoeff = 30
 
-        # parse the labels
-        self.field_degree, self.field_real_signature, self.field_absdisc, self.field_index  = map(int, self.field_label.split("."));
-        field_signature = [ self.field_real_signature, (self.field_degree - self.field_real_signature) // 2]
-
-        self.ec_conductor_norm  = int(self.conductor_label.split(".")[0])
-
-
-        self.conductor = self.ec_conductor_norm * (self.field_absdisc ** self.field_degree)
-
-        # Load data from the database
-        self.long_isogeny_class_label = self.conductor_label + '.' + self.isogeny_class_label
-
-        # I'm not sure what this is used for
-        if self.field_degree == 1:
-            self.label = self.long_isogeny_class_label;
-        if self.field_degree != 1:
-            self.label = self.field_label + "." + self.long_isogeny_class_label;
+        # Put the arguments into the object dictionary
+        self.__dict__.update(kwargs)
 
-        self.field = "Q" if self.field_degree == 1 else self.field_label;
-        isogeny_class_url = "EllipticCurve/%s/%s/%s" % (self.field, self.conductor_label, self.isogeny_class_label,)
-        self.lfunc_data = LfunctionDatabase.getInstanceLdata(isogeny_class_url)
-        if not self.lfunc_data:
-            raise KeyError('No L-function instance data for "%s" was found in the database.' % isogeny_class_url)
+        # Set field, conductor, isogeny information from labels
+        self._parse_labels()
 
+        self._retrieve_lfunc_data_from_db()
         # Extract the data
         makeLfromdata(self)
 
@@ -560,144 +596,191 @@ def __init__(self, **args):
         self.poles = []
         self.residues = []
         self.degree = self.field_degree * 2;
+        self.langlands = self.is_langlands()
 
-        if self.field_degree == 1 or (self.field_degree == 2 and self.field_real_signature == 2):
-            self.langlands = True;
-        else:
-            self.langlands = False;
+        self.initialize_webpage_data()
 
-        # number of actual Gamma functions
-        self.quasidegree = sum( field_signature );
+    def base_field(self):
+        """base_field of the EC"""
+        return self.field_label
 
-        ## Get the data for the corresponding modular form if possible
-        #self.get_modular_form();
+    def ground_field(self):
+        """Field of the Dirichlet coefficients"""
+        return 'Q'
 
-        # Text for the web page
-        self.texname = "L(s)"  # "L(s,E)"
-        self.htmlname = "<em>L</em>(<em>s</em>)"  # "<em>L</em>(<em>s,E</em>)"
-        self.texname_arithmetic = "L(s)"  # "L(E,s)"
-        self.htmlname_arithmetic = "<em>L</em>(<em>s</em>)"  # "<em>L</em>(<em>E,s</em>)"
-        self.texnamecompleteds = "\\Lambda(s)"  # "\\Lambda(s,E)"
-        self.texnamecompleted1ms = "\\Lambda(1-s)"  # "\\Lambda(1-s,E)"
-        self.texnamecompleteds_arithmetic = "\\Lambda(s)"  # "\\Lambda(E,s)"
-        self.texnamecompleted1ms_arithmetic = "\\Lambda(" + str(self.motivic_weight + 1) + "-s)"  # "\\Lambda(E, " + str(self.motivic_weight + 1) + "-s)"
-        #title_end = "where $E$ is an elliptic curve in isogeny class %s" % self.label
-        title_end = " of degree %d, weight 1, conductor %d, and trivial character" % (self.degree, self.conductor,)
+    def initialize_webpage_data(self):
+        self._set_web_displaynames()
+        self.info = self.general_webpagedata()
+        self._set_title()
         self.credit = ''
+        self._set_knowltype()
+        return
+
+    def is_langlands(self):
+        if self.field_degree == 1 or \
+                (self.field_degree == 2 and self.field_real_signature == 2):
+            return True
+        return False
+
+    @property
+    def bread(self):
+        """breadcrumbs for webpage"""
+        if self.base_field() == '1.1.1.1': #i.e. QQ
+            lbread = get_bread(2,
+                    [
+                      ('Elliptic curve', url_for('.l_function_ec_browse_page')),
+                      (self.label, url_for('.l_function_ec_page',
+                              conductor_label=self.conductor,
+                              isogeny_class_label = self.isogeny_class_label))
+                    ])
+        else:
+            lbread = get_bread(self.degree,
+                [
+                    # FIXME there is no .l_function_ecnf_browse_page
+                    #('Elliptic curve', url_for('.l_function_ec_browse_page')),
+                    (self.label,
+                     url_for('.l_function_ecnf_page',
+                            field_label = self.field_label,
+                            conductor_label = self.conductor_label,
+                            isogeny_class_label = self.long_isogeny_class_label))
+                ])
+        return lbread
+
+    @property
+    def factors(self):
+        """
+        If L-function factors as a product of other L-functions, this is a list
+        of those factors. Otherwise, this is an empty list.
+        """
+        lfactors = []
+        if "," in self.Lhash:
+            for factor_Lhash in  self.Lhash.split(","):
+                for instance in sorted(LfunctionDatabase
+                                       .get_instances_by_Lhash(factor_Lhash),
+                                       key=lambda elt: elt['url']):
+                    url = instance['url']
+                    name, obj_exists = name_and_object_from_url(url)
+                    if obj_exists:
+                        lfactors.append((name,  "/" + url))
+                    else:
+                        name += '&nbsp;  n/a';
+                        lfactors.append((name, ""))
+        return lfactors
+
+    @property
+    def field(self):
+        return "Q" if self.field_degree == 1 else self.field_label
+
+    @property
+    def friends(self):
+        """The 'friends' to show on webpage."""
+        lfriends = []
+        if self.base_field() == '1.1.1.1': #i.e. QQ
+            # only show symmetric powers for non-CM curves
+            if not isogeny_class_cm(self.label):
+                lfriends.append(('Symmetric square L-function',
+                                url_for(".l_function_ec_sym_page_label",
+                                    power='2', label=self.label)))
+                lfriends.append(('Symmetric cube L-function',
+                                url_for(".l_function_ec_sym_page_label",
+                                    power='3', label=self.label)))
+        return lfriends
+
+    @property
+    def instances(self):
+        # Currently elliptic curve L-fns don't track other instances of the
+        # L-fn, but the website expects this property to exist.
+        return []
+
+    @property
+    def label(self):
+        if self.field_degree == 1:
+            llabel = self.long_isogeny_class_label
+        else:
+            llabel = self.field_label + "." + self.long_isogeny_class_label
+        return llabel
+
+    @property
+    def origins(self):
+        lorigins = []
+        for instance in sorted(LfunctionDatabase
+                               .get_instances_by_Lhash(self.Lhash),
+                               key=lambda elt: elt['url']):
+            url = instance['url'];
+            name, obj_exists = name_and_object_from_url(url);
+            if not name:
+                name = ''
+            if obj_exists:
+                lorigins.append((name, "/"+url));
+            else:
+                name += '&nbsp;  n/a';
+                lorigins.append((name, ""));
+        if self.base_field() == '1.1.1.1': #i.e. QQ
+            #TODO replace classical modular forms origin by adding an object to the database
+            if self.conductor <= 101:
+                lorigins.append(
+                   ('Modular form ' + (self.long_isogeny_class_label).replace('.', '.2'),
+                       url_for("emf.render_elliptic_modular_forms",
+                       level=self.conductor, weight=2,
+                       character=1, label=self.isogeny_class_label)
+                   ))
+            else:
+                lorigins.append(('Modular form ' + (self.long_isogeny_class_label)
+                                .replace('.', '.2') +'&nbsp;  n/a', ""))
+        return lorigins
+
+    def _parse_labels(self):
+        """Set field, conductor, isogeny information from labels."""
+        (self.field_degree,
+            self.field_real_signature,
+            self.field_absdisc,
+            self.field_index)  = map(int, self.field_label.split("."))
+        field_signature = [self.field_real_signature,
+                (self.field_degree - self.field_real_signature) // 2]
+        # number of actual Gamma functions
+        self.quasidegree = sum( field_signature )
+        self.ec_conductor_norm  = int(self.conductor_label.split(".")[0])
+        self.conductor = self.ec_conductor_norm * (self.field_absdisc ** self.field_degree)
+        self.long_isogeny_class_label = self.conductor_label + '.' + self.isogeny_class_label
+        return
 
-        # Initiate the dictionary info that contains the data for the webpage
-        self.info = self.general_webpagedata()
 
-        # the /
+    def _retrieve_lfunc_data_from_db(self):
+        isogeny_class_url = "EllipticCurve/%s/%s/%s" % (self.field,
+                                                        self.conductor_label,
+                                                        self.isogeny_class_label)
+        self.lfunc_data = LfunctionDatabase.getInstanceLdata(isogeny_class_url)
+        if not self.lfunc_data:
+            raise KeyError('No L-function instance data for "%s" was found in the database.' % isogeny_class_url)
+        return
+
+    def _set_knowltype(self):
         if self.field_degree == 1:
             self.info['knowltype'] = "ec.q"
         else:
             self.info['knowltype'] = "ec.nf"
+        return
 
-
+    def _set_title(self):
+        title_end = (" of degree %d, weight 1, conductor %d,"
+                     " and trivial character" % (self.degree, self.conductor))
         self.info['title'] = "$" + self.texname + "$" + ", " + title_end
         self.info['title_arithmetic'] = "L-function "  + title_end
         self.info['title_analytic'] = "L-function " + title_end
+        return
 
+    def _set_web_displaynames(self):
+        self.texname = "L(s)"  # "L(s,E)"
+        self.htmlname = "<em>L</em>(<em>s</em>)"  # "<em>L</em>(<em>s,E</em>)"
+        self.texname_arithmetic = "L(s)"  # "L(E,s)"
+        self.htmlname_arithmetic = "<em>L</em>(<em>s</em>)"  # "<em>L</em>(<em>E,s</em>)"
+        self.texnamecompleteds = "\\Lambda(s)"  # "\\Lambda(s,E)"
+        self.texnamecompleted1ms = "\\Lambda(1-s)"  # "\\Lambda(1-s,E)"
+        self.texnamecompleteds_arithmetic = "\\Lambda(s)"  # "\\Lambda(E,s)"
+        self.texnamecompleted1ms_arithmetic = "\\Lambda(" + str(self.motivic_weight + 1) + "-s)"
+        # "\\Lambda(E, " + str(self.motivic_weight + 1) + "-s)"
+        return
 
-        # Field of the Dirichlet coefficients
-    def ground_field(self):
-        return 'Q';
-
-        # base_field of the EC
-    def base_field(self):
-        return self.field_label
-
-# This should be handled through instances database
-#    def get_modular_form(self):
-#        # Get the data for the corresponding modular form if possible
-#        # In the future we should perhaps have derived classes that overwrite this function
-#        self.modform = False;
-#        if self.field_degree == 1:
-#            # EC over QQ
-#            modform_translation_limit = 101
-#            if self.level <= modform_translation_limit:
-#                self.modform = modform_from_EC(self.long_isogeny_class_label)
-#        elif self.field_degree == 2:
-#            # quadratic extension
-#            if self.field_real_signature == 2:
-#                # if C.hmfs.forms.search.find({ u'label' : label }).count() >
-#                pass;
-#            elif self.field_real_signature == 0:
-#                # if C.bmfs.forms.search.find({ u'label' : label }).count() > 0
-#                pass;
-
-# DEPRECATED
-#class Lfunction_EC_Q(Lfunction):
-#    """Class representing an elliptic curve L-function
-#    It should be called with a dictionary of the forms:
-#
-#    dict = { 'conductor': ..., 'isogeny':  }
-#    """
-#
-#    def __init__(self, **args):
-#        constructor_logger(self, args)
-#        validate_required_args('Unable to construct elliptic curve L-function.',
-#                               args, 'conductor', 'isogeny')
-#
-#        self._Ltype = "ellipticcurveQ"
-#
-#        # Put the arguments into the object dictionary
-#        self.__dict__.update(args)
-#        self.numcoeff = 30
-#
-#        # Load data from the database
-#        self.label = self.conductor + '.' + self.isogeny
-#        label_slash = self.conductor + '/' + self.isogeny
-#        db_label = "EllipticCurve/Q/" + label_slash
-#        self.lfunc_data = LfunctionDatabase.getInstanceLdata(db_label)
-#        if not self.lfunc_data:
-#                raise KeyError('No L-function instance data for "%s" was found in the database.' % db_label)
-#
-#        # Extract the data
-#        makeLfromdata(self)
-#        self.fromDB = True
-#
-#        # Mandatory properties
-#        self.coefficient_period = 0
-#        self.coefficient_type = 2
-#        self.poles = []
-#        self.residues = []
-#        self.langlands = True
-#        self.quasidegree = 1
-#
-#        # Specific properties
-#        # Get the data for the corresponding modular form if possible
-#        modform_translation_limit = 101
-#        if self.level <= modform_translation_limit:
-#            self.modform = modform_from_EC(self.label)
-#        else:
-#            self.modform = False
-#
-#        # Text for the web page
-#        self.texname = "L(s)"  # "L(s,E)"
-#        self.htmlname = "<em>L</em>(<em>s</em>)"  # "<em>L</em>(<em>s,E</em>)"
-#        self.texname_arithmetic = "L(s)"  # "L(E,s)"
-#        self.htmlname_arithmetic = "<em>L</em>(<em>s</em>)"  # "<em>L</em>(<em>E,s</em>)"
-#        self.texnamecompleteds = "\\Lambda(s)"  # "\\Lambda(s,E)"
-#        self.texnamecompleted1ms = "\\Lambda(1-s)"  # "\\Lambda(1-s,E)"
-#        self.texnamecompleteds_arithmetic = "\\Lambda(s)"  # "\\Lambda(E,s)"
-#        self.texnamecompleted1ms_arithmetic = "\\Lambda(" + str(self.motivic_weight + 1) + "-s)"  # "\\Lambda(E, " + str(self.motivic_weight + 1) + "-s)"
-#        #title_end = "where $E$ is an elliptic curve in isogeny class %s" % self.label
-#        title_end = " of degree 2, weight 1, conductor %s, and trivial character" % self.conductor
-#        self.credit = ''
-#
-#        # Initiate the dictionary info that contains the data for the webpage
-#        self.info = self.general_webpagedata()
-#        self.info['knowltype'] = "ec.q"
-#        self.info['title'] = "$" + self.texname + "$" + ", " + title_end
-#        self.info['title_arithmetic'] = "L-function "  + title_end
-#        self.info['title_analytic'] = "L-function " + title_end
-#
-#    def ground_field(self):
-#        return "Q"
-
-#############################################################################
 
 class Lfunction_EMF(Lfunction):
     """Class representing an elliptic modular form L-function
@@ -712,11 +795,10 @@ class Lfunction_EMF(Lfunction):
     def __init__(self, **args):
         constructor_logger(self, args)
 
-        # Check for compulsory arguments
-        validate_required_args ('Unable to construct elliptic modular form L-function.',
-                                args, 'weight','level','character','label','number')
-        validate_integer_args ('Unable to construct elliptic modular form L-function.',
-                               args, 'weight','level','character','number')
+        validate_required_args('Unable to construct elliptic modular form L-function.',
+                               args, 'weight','level','character','label','number')
+        validate_integer_args('Unable to construct elliptic modular form L-function.',
+                              args, 'weight','level','character','number')
 
         self._Ltype = "ellipticmodularform"
 
@@ -1818,18 +1900,3 @@ def __init__(self, **args):
         generateSageLfunction(self)
 
         constructor_logger(self, args)
-
-#############################################################################
-
-
-# class GaloisRepresentationLfunction(Lfunction, GaloisRepresentation):
-#    """
-#    Class representing the L-function of a general galois representation
-#    This is mainly used for twisting two such constructed from other
-#    classes above.
-#
-#    Most of the values are inherited from GaloisRepresentation, where
-#    things are done a bit more systematic.
-#    """
-#
-# This is implemented in lmfdb.tensor_products.galois_reps instead.
diff --git a/lmfdb/lfunctions/Lfunctionutilities.py b/lmfdb/lfunctions/Lfunctionutilities.py
--- a/lmfdb/lfunctions/Lfunctionutilities.py
+++ b/lmfdb/lfunctions/Lfunctionutilities.py
@@ -2,12 +2,18 @@
 
 import re
 from lmfdb.lfunctions import logger
+from flask import url_for
 import math
 from sage.all import ZZ, QQ, RR, CC, Rational, RationalField, ComplexField, PolynomialRing, LaurentSeriesRing, O, Integer, Primes, primes, CDF, I, real_part, imag_part, latex, factor, prime_divisors, prime_pi, exp, pi, prod, floor
 from lmfdb.genus2_curves.web_g2c import list_to_factored_poly_otherorder
 from lmfdb.transitive_group import group_display_knowl
 from lmfdb.base import getDBConnection
 from lmfdb.utils import truncate_number
+from lmfdb.elliptic_curves.web_ec import is_ec_isogeny_class_in_db
+from lmfdb.ecnf.WebEllipticCurve import is_ecnf_isogeny_class_in_db
+from lmfdb.hilbert_modular_forms.web_HMF import is_hmf_in_db
+from lmfdb.bianchi_modular_forms.web_BMF import is_bmf_in_db
+from lmfdb.modular_forms.elliptic_modular_forms.backend.emf_utils import is_newform_in_db
 
 ###############################################################
 # Functions for displaying numbers in correct format etc.
@@ -64,7 +70,7 @@ def string2number(s):
     except:
         return s
 
-    
+
 def pair2complex(pair):
     ''' Turns the pair into a complex number.
     '''
@@ -121,7 +127,7 @@ def seriescoeff(coeff, index, seriescoefftype, seriestype, truncationexp, precis
     except TypeError:     # mostly a hack for Dirichlet L-functions
         if seriescoefftype == "serieshtml":
             if coeff == "I":
-                return " + " + "$i$" + "&middot;" + seriesvar(index, seriestype) 
+                return " + " + "$i$" + "&middot;" + seriesvar(index, seriestype)
             elif coeff == "-I":
                 return "&minus;" + " $i$" + "&middot;" + seriesvar(index, seriestype)
             else:
@@ -295,7 +301,7 @@ def lfuncDShtml(L, fmt):
 #    if fmt == "analytic" or fmt == "langlands":
     if fmt in ["analytic", "langlands", "arithmetic"]:
         ans += "<table class='dirichletseries'><tr>"
-        ans += "<td valign='top'>"  # + "$" 
+        ans += "<td valign='top'>"  # + "$"
         if fmt == "arithmetic":
             ans += "<span class='term'>"
             ans += L.htmlname_arithmetic
@@ -333,9 +339,9 @@ def lfuncDShtml(L, fmt):
                     "serieshtml", "dirichlethtml", -6, 5)
             if tmp != "":
                 nonzeroterms += 1
-            ans = ans + " <span class='term'>" + tmp + "</span> "  
+            ans = ans + " <span class='term'>" + tmp + "</span> "
                 # need a space between spans to allow line breaks. css stops a break within a span
-     
+
             if nonzeroterms > maxcoeffs:
                 break
             if(nonzeroterms % numperline == 0):
@@ -466,18 +472,18 @@ def lfuncEPhtml(L,fmt):
     for lf in L.bad_lfactors:
         try:
             thispolygal = list_to_factored_poly_otherorder(lf[1], galois=True)
-            eptable += ("<tr><td>" + goodorbad + "</td><td>" + str(lf[0]) + "</td><td>" + 
+            eptable += ("<tr><td>" + goodorbad + "</td><td>" + str(lf[0]) + "</td><td>" +
                         "$" + thispolygal[0] + "$" +
                         "</td>")
             if L.degree > 2:
-                eptable += "<td class='galois'>" 
+                eptable += "<td class='galois'>"
                 this_gal_group = thispolygal[1]
                 if this_gal_group[0]==[0,0]:
                     pass   # do nothing, because the local faco is 1
                 elif this_gal_group[0]==[1,1]:
-                    eptable += group_display_knowl(this_gal_group[0][0],this_gal_group[0][1],C,'$C_1$') 
+                    eptable += group_display_knowl(this_gal_group[0][0],this_gal_group[0][1],C,'$C_1$')
                 else:
-                    eptable += group_display_knowl(this_gal_group[0][0],this_gal_group[0][1],C) 
+                    eptable += group_display_knowl(this_gal_group[0][0],this_gal_group[0][1],C)
                 for j in range(1,len(thispolygal[1])):
                     eptable += "$\\times$"
                     eptable += group_display_knowl(this_gal_group[j][0],this_gal_group[j][1],C)
@@ -500,7 +506,7 @@ def lfuncEPhtml(L,fmt):
         if L.degree > 2:
             eptable += "<td class='galois'>"
             this_gal_group = thispolygal[1]
-            eptable += group_display_knowl(this_gal_group[0][0],this_gal_group[0][1],C) 
+            eptable += group_display_knowl(this_gal_group[0][0],this_gal_group[0][1],C)
             for j in range(1,len(thispolygal[1])):
                 eptable += "$\\times$"
                 eptable += group_display_knowl(this_gal_group[j][0],this_gal_group[j][1],C)
@@ -577,7 +583,7 @@ def lfuncEpSymPower(L):
         ans += poly_string
     ans += '\\prod_{p \\nmid %d }\\prod_{j=0}^{%d} ' % (L.E.conductor(),L.m)
     ans += '\\left(1- \\frac{\\alpha_p^j\\beta_p^{%d-j}}' % L.m
-    ans += '{p^{s}} \\right)^{-1}'    
+    ans += '{p^{s}} \\right)^{-1}'
     return ans
 
 #---------
@@ -889,7 +895,7 @@ def getConductorIsogenyFromLabel(label):
             while iso[0].isdigit():
                 cond += iso[0]
                 iso = iso[1:]
-                
+
         # Strip off the curve number
         while iso[-1].isdigit():
             iso = iso[:-1]
@@ -897,6 +903,59 @@ def getConductorIsogenyFromLabel(label):
 
     except:
         return None, None
-    
-
 
+#######################################################################
+# Functions for interacting with web structure
+#######################################################################
+
+# TODO This needs to be able to handle any sort of L-function.
+# There should probably be a more relevant field
+# in the database, instead of trying to extract this from a URL
+def name_and_object_from_url(url):
+    url_split = url.split("/");
+    name = None;
+    obj_exists = False;
+
+    if url_split[0] == "EllipticCurve":
+        if url_split[1] == 'Q':
+            # EllipticCurve/Q/341641/a
+            label_isogeny_class = ".".join(url_split[-2:]);
+            # count doesn't honor limit!
+            obj_exists = is_ec_isogeny_class_in_db(label_isogeny_class);
+        else:
+            # EllipticCurve/2.2.140.1/14.1/a
+            label_isogeny_class =  "-".join(url_split[-3:]);
+            obj_exists = is_ecnf_isogeny_class_in_db(label_isogeny_class);
+        name = 'Isogeny class ' + label_isogeny_class;
+
+    elif url_split[0] == "ModularForm":
+        if url_split[1] == 'GL2':
+            if url_split[2] == 'Q' and url_split[3]  == 'holomorphic':
+                # ModularForm/GL2/Q/holomorphic/14/2/1/a
+                full_label = ".".join(url_split[-4:])
+                name =  'Modular form ' + full_label;
+                obj_exists = is_newform_in_db(full_label);
+
+            elif  url_split[2] == 'TotallyReal':
+                # ModularForm/GL2/TotallyReal/2.2.140.1/holomorphic/2.2.140.1-14.1-a
+                label = url_split[-1];
+                name =  'Hilbert modular form ' + label;
+                obj_exists = is_hmf_in_db(label);
+
+            elif url_split[2] ==  'ImaginaryQuadratic':
+                # ModularForm/GL2/ImaginaryQuadratic/2.0.4.1/98.1/a
+                label = '-'.join(url_split[-3:])
+                name = 'Bianchi modular form ' + label;
+                obj_exists = is_bmf_in_db(label);
+    return name, obj_exists
+
+def get_bread(degree, breads=[]):
+    """
+    Returns the two top levels of bread crumbs plus the ones supplied in breads.
+    """
+    breadcrumb = [('L-functions', url_for('.l_function_top_page')),
+          ('Degree ' + str(degree),
+           url_for('.l_function_degree_page', degree='degree' + str(degree)))]
+    for b in breads:
+        breadcrumb.append(b)
+    return breadcrumb
diff --git a/lmfdb/lfunctions/main.py b/lmfdb/lfunctions/main.py
--- a/lmfdb/lfunctions/main.py
+++ b/lmfdb/lfunctions/main.py
@@ -19,9 +19,9 @@
                        RiemannZeta, DedekindZeta, ArtinLfunction, SymmetricPowerLfunction,
                        HypergeometricMotiveLfunction, Lfunction_genus2_Q, Lfunction_lcalc,
                        Lfunction_from_db)
-from LfunctionDatabase import get_instances_by_Lhash
 from LfunctionComp import isogeny_class_table, isogeny_class_cm
-from Lfunctionutilities import p2sage, styleTheSign, getConductorIsogenyFromLabel
+from Lfunctionutilities import (p2sage, styleTheSign, get_bread,
+                                getConductorIsogenyFromLabel)
 from lmfdb.utils import to_dict
 from lmfdb.WebCharacter import WebDirichlet
 from lmfdb.lfunctions import l_function_page
@@ -204,17 +204,6 @@ def set_info_for_start_page():
 
     return info
 
-
-def get_bread(degree, breads=[]):
-    ''' Returns the two top levels of bread crumbs plus the ones supplied in breads.
-    '''
-    bc = [('L-functions', url_for('.l_function_top_page')),
-          ('Degree ' + str(degree), url_for('.l_function_degree_page', degree='degree' + str(degree)))]
-    for b in breads:
-        bc.append(b)
-    return bc
-
-
 ################################################################################
 #   Route functions, individual L-function homepages
 ################################################################################
@@ -513,71 +502,11 @@ def set_bread_and_friends(L, request):
         bread = get_bread(1, [(charname, request.url)])
 
     elif L.Ltype() == 'ellipticcurve':
-
-        for instance in sorted(get_instances_by_Lhash(L.Lhash), key=lambda elt: elt['url']):
-            url = instance['url'];
-            name, obj_exists = name_and_object_from_url(url);
-            if obj_exists:
-                origins.append((name, "/"+url));
-            else:
-                name += '&nbsp;  n/a';
-                origins.append((name, ""));
-
-        if "," in L.Lhash:
-            for factor_Lhash in  L.Lhash.split(","):
-                for instance in sorted(get_instances_by_Lhash(factor_Lhash), key=lambda elt: elt['url']):
-                    url = instance['url'];
-                    name, obj_exists = name_and_object_from_url(url);
-                    if obj_exists:
-                        factors.append((name,  "/" + url));
-                    else:
-                        name += '&nbsp;  n/a';
-                        factors.append((name, ""));
-
-        if L.base_field() == '1.1.1.1': # i.e., QQ
-            label = L.label
-            if not isogeny_class_cm(label): # only show symmetric powers for non-CM curves
-                friends.append(
-                ('Symmetric square L-function', url_for(".l_function_ec_sym_page_label",
-                                                        power='2', label=label)))
-                friends.append(
-                ('Symmetric cube L-function', url_for(".l_function_ec_sym_page_label", power='3', label=label)))
-
-            bread = get_bread(2,
-                    [
-                        ('Elliptic curve', url_for('.l_function_ec_browse_page')),
-                        (label,
-                            url_for('.l_function_ec_page',
-                                conductor_label=L.conductor,
-                                isogeny_class_label = L.isogeny_class_label
-                                )
-                         )
-                        ])
-
-            #TODO replace classical modular forms origin by adding an object to the database
-            if L.conductor <= 101:
-                origins.append(
-                        ('Modular form ' + (L.long_isogeny_class_label).replace('.', '.2'),
-                            url_for("emf.render_elliptic_modular_forms",
-                            level=L.conductor, weight=2,
-                            character=1, label=L.isogeny_class_label)
-                            ))
-            else:
-                origins.append(('Modular form ' + (L.long_isogeny_class_label).replace('.', '.2') +'&nbsp;  n/a', ""))
-
-        else:
-            bread = get_bread(
-                    L.degree ,
-                    [
-                        # FIXME there is no .l_function_ecnf_browse_page
-                        ('Elliptic curve', url_for('.l_function_ec_browse_page')),
-                        (L.label,
-                            url_for('.l_function_ecnf_page',
-                                field_label = L.field_label,
-                                conductor_label = L.conductor_label ,
-                                isogeny_class_label = L.long_isogeny_class_label)
-                        )
-                    ])
+        bread = L.bread
+        origins = L.origins
+        friends = L.friends
+        factors = L.factors
+        instances = L.instances
 
     elif L.Ltype() == 'ellipticmodularform':
         friendlink = friendlink.rpartition('/')[0] # Strips off the embedding
@@ -727,32 +656,11 @@ def ordinal(n):
             bread = [('L-functions', url_for('.l_function_top_page'))]
 
     elif L.Ltype() == "general":
-        bread = [('L-functions', url_for('.l_function_top_page'))]
-
-        for instance in sorted(get_instances_by_Lhash(L.Lhash), key=lambda elt: elt['url']):
-            url = instance['url'];
-            instances.append((str(url), "/L/" + url))
-            name, obj_exists = name_and_object_from_url(url);
-
-            if not name:
-                name = ""
-
-            if obj_exists:
-                origins.append((name, "/"+url));
-            else:
-                name += '&nbsp;  n/a';
-                origins.append((name, ""));
-
-        if "," in L.Lhash:
-            for factor_Lhash in  L.Lhash.split(","):
-                for instance in sorted(get_instances_by_Lhash(factor_Lhash), key=lambda elt: elt['url']):
-                    url = instance['url'];
-                    name, obj_exists = name_and_object_from_url(url);
-                    if obj_exists:
-                        factors.append((name,  "/" + url));
-                    else:
-                        name += '&nbsp;  n/a';
-                        factors.append((name, ""));
+        bread = L.bread
+        origins = L.origins
+        friends = L.friends
+        factors = L.factors
+        instances = L.instances
 
     return (bread, origins, friends, factors, instances)
 
@@ -1288,51 +1196,3 @@ def processSymPowerEllipticCurveNavigation(startCond, endCond, power):
 
     s += '</table>\n'
     return s
-
-
-# TODO This needs to be able to handl any sort of L-function.
-#      There should probably be a more relevant field
-#      in the database, instead of trying to extract this from a URL
-def name_and_object_from_url(url):
-    from lmfdb.elliptic_curves.web_ec import is_ec_isogeny_class_in_db
-    from lmfdb.ecnf.WebEllipticCurve import is_ecnf_isogeny_class_in_db
-    from lmfdb.hilbert_modular_forms.web_HMF import is_hmf_in_db
-    from lmfdb.bianchi_modular_forms.web_BMF import is_bmf_in_db
-    from lmfdb.modular_forms.elliptic_modular_forms.backend.emf_utils import is_newform_in_db
-    url_split = url.split("/");
-    name = None;
-    obj_exists = False;
-
-    if url_split[0] == "EllipticCurve":
-        if url_split[1] == 'Q':
-            # EllipticCurve/Q/341641/a
-            label_isogeny_class = ".".join(url_split[-2:]);
-            # count doesn't honor limit!
-            obj_exists = is_ec_isogeny_class_in_db(label_isogeny_class);
-        else:
-            # EllipticCurve/2.2.140.1/14.1/a
-            label_isogeny_class =  "-".join(url_split[-3:]);
-            obj_exists = is_ecnf_isogeny_class_in_db(label_isogeny_class);
-        name = 'Isogeny class ' + label_isogeny_class;
-
-    elif url_split[0] == "ModularForm":
-        if url_split[1] == 'GL2':
-            if url_split[2] == 'Q' and url_split[3]  == 'holomorphic':
-                # ModularForm/GL2/Q/holomorphic/14/2/1/a
-                full_label = ".".join(url_split[-4:])
-                name =  'Modular form ' + full_label;
-                obj_exists = is_newform_in_db(full_label);
-
-            elif  url_split[2] == 'TotallyReal':
-                # ModularForm/GL2/TotallyReal/2.2.140.1/holomorphic/2.2.140.1-14.1-a
-                label = url_split[-1];
-                name =  'Hilbert modular form ' + label;
-                obj_exists = is_hmf_in_db(label);
-
-            elif url_split[2] ==  'ImaginaryQuadratic':
-                # ModularForm/GL2/ImaginaryQuadratic/2.0.4.1/98.1/a
-                label = '-'.join(url_split[-3:])
-                name = 'Bianchi modular form ' + label;
-                obj_exists = is_bmf_in_db(label);
-
-    return name, obj_exists


Test patch: